Reputation: 427
I'd like to have a base class that has a constant field (like an unique ID associated with the class that can't be modified after compile time). So far the static const
declaration would be just fine. Now, I'd like to inherit this base class and make sure that the children of this class do have the same field, but with their own values. How can I do this?
Let's say, I'd like to have a base class called Base
with an ID
field that holds the int
value of 0. Then, I'd like to have the classes A
, B
and C
, all of them being public children of Base
and I'd like to make sure that these children would also have the ID
fields with the respective values of 1, 2 and 3 (by 'making sure', I mean something like getting a compiler error if they don't have a ID explicitly declared).
If I could manage to build this scenario, my expectation would be that asking for the ID
field of a Base*
pointer, I should get different values depending whether the pointer was created as new A()
, new B()
or new C()
.
My guess would be to declare ID
as virtual static const
, which of course doesn't make sense and gives a compiler error.
But then what can I do to achieve the described result? (The only thing that I could imagine would be to declare ID
as a virtual function returning an integer and then hard-code the value into the function body, but I'm looking for something more elegant.)
Thank you in advance!
Upvotes: 24
Views: 13730
Reputation: 2934
It would indeed be very useful to have virtual static members in C++. They could easily be added to the language, no new keywords are necessary. The following code for example names shape types for a graphics library:
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 };
...
};
This declares Shape
as an abstract base class, as the construction of Shape::name
and Shape::closed
are explicitely skipped via = delete
.
The space for virtual static members could be allocated in the same VTable, that is already used for virtual function calls. If all virtual static members are constinit
(new with C++20) or constexpr
, then the VTable can be written to read-only memory, where most compilers write it currently, too. If not, then the VTable has to be placed in read-write memory instead.
In general, virtual static members don't need to be const
, they could also be read-write.
Virtual static members could both be accessed with the classname as a prefix (where they will behave like normal static members) or via an object, where the object's VTable pointer will be used to access the right VTable.
As long, as they are not in the standard, they can be emulated using virtual functions, that return a reference to a local static variable:
virtual const std::string& get_name() const {
static const std::string name { "circle" };
return name;
}
In case, that a derived class does not override the static member (respectively the virtual getter function), the semantics between the real virtual static members and the emulated virtual static members are a bit different: The real virtual static member between parent and child class would actually refer to different instances of that object, with the constructor called for the parent and each child, which doesn't override the virtual static member. But the emulated getter function would always return a reference to exactly the same object. On read-only virtual static members, this shouldn't make a difference (unless the constructor actually initializes each instance differently), but on read-write virtual static members, updating them would make a difference.
Upvotes: 0
Reputation: 258578
A static
method cannot be virtual
, and no data members can be virtual
.
But you can hide static
fields in derived classes and use a virtual
method to return them.
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; }
};
Alternative:
class A
{
public:
A(int id = 0) : ID(id) {}
const int ID;
getID() { return ID; }
};
class B : public A
{
public:
B() : A(1) {}
};
Upvotes: 21