Възможно ли е да се декларира виртуална статична постоянна стойност в C++ клас?

Бих искал да имам базов клас, който има постоянно поле (като уникален идентификатор, свързан с класа, който не може да бъде модифициран след време на компилиране). Досега декларацията static const ще бъде добре. Сега бих искал да наследя този базов клас и да се уверя, че децата на този клас имат същото поле, но със свои собствени стойности. Как мога да направя това?

Да кажем, че бих искал да имам базов клас, наречен Base с поле ID, което съдържа int стойност от 0. След това бих искал да имам класовете A, B и C, като всички те са публични деца на Base и бих искал да се уверя, че тези деца също ще имат полетата ID със съответните стойности 1, 2 и 3 (под „уверяване“ имам предвид нещо като получаване на грешка на компилатора, ако нямат идентификатор изрично декларирано).

Ако успея да изградя този сценарий, моите очаквания биха били, че като поискам полето ID на указател Base*, трябва да получа различни стойности в зависимост от това дали указателят е създаден като new A(), new B() или new C().

Моето предположение би било да декларирам ID като virtual static const, което разбира се няма смисъл и дава грешка на компилатора.

Но тогава какво мога да направя, за да постигна описания резултат? (Единственото нещо, което мога да си представя, е да декларирам ID като виртуална функция, връщаща цяло число и след това да кодирам твърдо стойността в тялото на функцията, но търся нещо по-елегантно.)

Благодаря ви предварително!


person Siska Ádám    schedule 06.06.2012    source източник
comment
Полиморфизмът работи само за членски функции и не е валиден за членове с данни.   -  person Mahesh    schedule 06.06.2012
comment
Написах алтернатива, базирана на шаблони: stackoverflow.com/a/36797199/1529139   -  person 56ka    schedule 22.04.2016


Отговори (2)


Метод static не може да бъде virtual и никакви членове на данни не могат да бъдат virtual.

Но можете да скриете static полета в производни класове и да използвате virtual метод, за да ги върнете.

class A
{
public:
    static const int ID = 0;
    virtual int getID() { return A::ID; }
};
class B : A
{
public:
    static const int ID = 1;
    virtual int getID() { return B::ID; }
};

алтернатива:

class A
{
public:
    A(int id = 0) : ID(id) {}
    const int ID;
    getID() { return ID; }
};
class B : public A
{
public:
    B() : A(1) {}
};
person Luchian Grigore    schedule 06.06.2012
comment
Да, това е единственото решение, което знаех досега. Но няма ли начин това да се реши по по-елегантен/прост начин? Или ако това е единственият начин, тогава въпросът ми е по-скоро: каква е причината виртуалното наследяване, което работи с функции, да не работи с членове на данни? Разбирам защо всъщност static virtual няма да има смисъл, но не мога наистина да разбера защо, да кажем, просто поле virtual const int няма да има смисъл... - person Siska Ádám; 06.06.2012
comment
@SiskaÁdám, защото полиморфното поведение не е дефинирано за членове на данни. Това е начинът, по който е проектиран езикът. И това е хубаво нещо IMO. - person Luchian Grigore; 06.06.2012
comment
@SiskaÁdám Публикувах алтернатива, но все още намирам първата за по-добра. - person Luchian Grigore; 06.06.2012
comment
Какво е лудо, в C++11 можете дори да направите виртуалната функция constexpr. Което не е много полезно, тъй като няма да успее да се компилира, когато всъщност използвате виртуално наследяване. Въпреки че f<A().getID()>(); всъщност компилира, дадено f е шаблонна функция, приемаща цяло число... - person Damon; 06.06.2012
comment
@LuchianGrigore Разбирам. Това беше моментът, който вероятно пропуснах. Бихте ли обаче обяснили с няколко думи защо това е добре? Просто не мога да се сетя за пример, при който полиморфизмът на член на данните може да доведе до повече проблеми, отколкото полиморфизмът на функцията. Благодаря, Ádám - person Siska Ádám; 06.06.2012
comment
@SiskaÁdám обикновено членовете обозначават състояние. Ако използвате наследяване, приемате, че клас B е клас A и има всички свои членове плюс други, но има членове на A. Без отмяна. Те могат да бъдат различни, но не и отменени. Не мога да намеря пример, където това би било полезно, освен този, който публикувахте, което вероятно не е добър дизайн - има друг механизъм, който може да се използва за определяне на типа време на изпълнение на обект (вижте RTTI). Това ID нещо изглежда като хак. Може би това е само аз, защото не съм свикнал с концепцията за полиморфизъм на членовете на данните, но честно казано не мога да видя никаква полезност. - person Luchian Grigore; 06.06.2012
comment
@LuchianGrigore Благодаря за помощта. Всъщност въпросът с ID беше само фиктивен пример. В моя действителен сценарий имам абстрактен базов клас за команди за рисуване и наследените класове ще бъдат действителните команди за рисуване. От съображения за ефективност координатите, използвани от командите за чертане, се съхраняват отделно в масив (имайки всички точки една след друга в блок памет, мога да правя трансформации по-бързо) и командите за чертане просто държат указател към своите данни. Наследената константа, за която мечтаех, е действителният брой точки, изискван от различните команди за рисуване. - person Siska Ádám; 06.06.2012
comment
@SiskaÁdám Мисля, че ако имате нужда от нещо подобно, вашият дизайн е дефектен. Но това би бил твърде широк въпрос за SO. Предлагам ви да прочетете Head First Design Patterns (без връзка). - person Luchian Grigore; 06.06.2012
comment
@Damon: Имате предвид виртуално наследяване или обикновено наследяване и използването на виртуална функция? Тези са напълно различни. Във всеки случай, constexpr във виртуална функция има смисъл за случаите, когато динамичното изпращане е деактивирано (или окончателното отменяне е известно на мястото на извикване по време на компилиране, или динамичното изпращане е изрично деактивирано) - person David Rodríguez - dribeas; 06.06.2012
comment
@DavidRodríguez-dribeas: Първото се има предвид, напр. A a; [...] f<a.getID()>;. Това (по доста очевидна причина) няма да успее да се компилира (в края на краищата това не е доказуема константа за време на компилация). Лудост е, че все пак можете да направите виртуалната функция constexpr, и тя наистина работи по предназначение също със статично изпращане (напр. f<A().getID()>;). - person Damon; 06.06.2012
comment
@Damon: Така че това не е първото, а второто: обикновено наследяване и виртуални функции (Виртуалното наследяване е съвсем различен звяр). AFAIK, вашият пример трябва да се компилира. Тъй като a е в обхвата, където се осъществява достъп (т.е. извикването на getID() не се извършва чрез препратка/указател), което не изисква динамично изпращане, което означава, че подходящото заместване може да бъде открито по време на компилиране и това би позволило вграждане на функцията в C++03, което от своя страна означава, че не трябва да има проблем при използването на това конкретно извикване като constexpr. - person David Rodríguez - dribeas; 06.06.2012
comment
Всъщност стандартът изрично забранява 'constexpr' да се използва във виртуални методи. Някои компилатори може все още да го позволяват, но не е стандарт. - person Meta; 25.09.2016

Наистина би било много полезно да има виртуални статични членове в C++. Те могат лесно да бъдат добавени към езика, не са необходими нови ключови думи. Следният код например назовава типове фигури за графична библиотека:

class Shape {
public:
    static constinit virtual std::string name = delete;
    static constexpr virtual bool closed = delete;
    ...
};

class Circle : public Shape {
public:
    static constinit std::string name override { "circle" };
    static constexpr bool close override { true };
    ...
};

class Line : public Shape {
public:
    static constinit std::string name override { "line" };
    static constexpr bool close override { false };
    ...
};

Това декларира Shape като абстрактен базов клас, тъй като конструкцията на Shape::name и Shape::closed изрично се пропуска чрез = delete.

Пространството за виртуални статични членове може да бъде разпределено в същата VTable, която вече се използва за извиквания на виртуални функции. Ако всички виртуални статични членове са constinit (ново с C++20) или constexpr, тогава VTable може да бъде записана в памет само за четене, където повечето компилатори също я записват в момента. Ако не, тогава VTable трябва да се постави в памет за четене и запис.

По принцип не е необходимо виртуалните статични членове да са const, те също могат да бъдат четене-запис.

Виртуалните статични членове могат да бъдат достъпни с името на класа като префикс (където те ще се държат като нормални статични членове) или чрез обект, където указателят на VTable на обекта ще се използва за достъп до правилната VTable.

Докато не са в стандарта, те могат да бъдат емулирани с помощта на виртуални функции, които връщат препратка към локална статична променлива:

virtual const std::string& get_name() const {
    static const std::string name { "circle" };
    return name;
}

В случай, че производен клас не отменя статичния член (съответно виртуалната функция за получаване), семантиката между реалните виртуални статични членове и емулираните виртуални статични членове е малко по-различна: Истинският виртуален статичен член между родителския и дъщерния клас всъщност ще се отнася до различни екземпляри на този обект, като конструкторът се извиква за родителя и всяко дете, което не отменя виртуалния статичен член. Но емулираната функция за получаване винаги ще върне препратка към точно същия обект. При виртуални статични членове само за четене това не би трябвало да има значение (освен ако конструкторът действително не инициализира всеки екземпляр по различен начин), но при виртуални статични членове за четене и запис актуализирането им би имало значение.

person Kai Petzke    schedule 20.10.2020