Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

About placing member variable into a pure virtual base class

94 views
Skip to first unread message

JiiPee

unread,
Oct 11, 2018, 6:04:11 PM10/11/18
to
They say (Bjarne?) that pure virtual class should not have any
data/member variables. Is this true? I created an example where I think
member variable seems to belong to the pure virtual base class:

#####################

class Animal
{
public:
    Animal(int age) : age_{age} {
    }
    virtual ~Animal() = 0; // make it pure virtual
    virtual void talk() {
    }
private:
    int age_;
};

inline Animal::~Animal()
{
}

class Dog : public Animal
{
public:
    Dog(int age) : Animal{ age } {
    }
    void talk() override {
        cout << "Meouw";
    }
};

##################

Where is the correct place for age_ assuming that *all animals have
age*. So lets assume that 100% all animals will have age-variable (they
need it). But its logical that Animal is pure virtual. So, where should
age_ belong to if not inside Animal class?


Or did I misunderstand something? I think somebody said pure virtual
interfaces should not have data/member variables. Did I misunderstand it?

Mr Flibble

unread,
Oct 11, 2018, 6:10:40 PM10/11/18
to
The only reason to make a destructor pure is to force a class that would
otherwise not be abstract to be abstract to force derivation but this is
rarely necessary. If the class needs to be abstract it will have other
pure virtual methods. Perhaps you should make talk() pure virtual instead?

It is perfectly fine for abstract base classes to have member variables
unless the class is representing an *interface* in which case the
idiomatic thing to do is to not have any member variables.

/Flibble

--
"Suppose it’s all true, and you walk up to the pearly gates, and are
confronted by God," Bryne asked on his show The Meaning of Life. "What
will Stephen Fry say to him, her, or it?"
"I’d say, bone cancer in children? What’s that about?" Fry replied.
"How dare you? How dare you create a world to which there is such misery
that is not our fault. It’s not right, it’s utterly, utterly evil."
"Why should I respect a capricious, mean-minded, stupid God who creates a
world that is so full of injustice and pain. That’s what I would say."

Alf P. Steinbach

unread,
Oct 11, 2018, 6:57:45 PM10/11/18
to
On 12.10.2018 00:03, JiiPee wrote:
> They say (Bjarne?) that pure virtual class should not have any
> data/member variables.

“Pure virtual class” is not standard terminology. I think you mean a
pure interface class. Which is not standard C++ terminology, but a
descriptive term that indicates how the class is used.

A pure interface will usually not have data members, but it's a feature
of C++ that it /can/ have data members. One reason to not have data
members is that without data members, one can inherit from that
interface multiple times with no ill effects, e.g., an animal will not
wind up with multiple different ages. To avoid such problems, with data
members one is essentially restricted to either single inheritance, or
virtual inheritance, and the latter carries some run time overhead. But
for any given case this restriction may not necessarily matter, and it
must be balanced against the advantages such as simplicity with data
members versus complexity without, i.e. the usefulness of data members.

Non-virtual member functions can also be useful, e.g. as wrappers that
check preconditions and postconditions for the called virtual functions.


> Is this true? I created an example where I think
> member variable seems to belong to the pure virtual base class:
>
> #####################
>
> class Animal
> {
> public:
>     Animal(int age) : age_{age} {
>     }
>     virtual ~Animal() = 0; // make it pure virtual
>     virtual void talk() {
>     }
> private:
>     int age_;
> };

Consider what happens as time goes by. The animals will not age. If you
want them to have age values that reflect the passage of time in the
real world, you can use a birthdate (including the year, of course).


> inline Animal::~Animal()
> {
> }
>
> class Dog : public Animal
> {
> public:
>     Dog(int age) : Animal{ age } {
>     }
>     void talk() override {
>         cout << "Meouw";
>     }
> };
>
> ##################
>
> Where is the correct place for age_ assuming that *all animals have
> age*. So lets assume that 100% all animals will have age-variable (they
> need it). But its logical that Animal is pure virtual. So, where should
> age_ belong to if not inside Animal class?

Putting the known common implementation and/or implementation support in
the base is generally OK.

There can be reasons not to do it, such as supporting general use of the
interface for arbitrary classes (you'd not want to impose per-instance
overhead on those not-really-animal classes), including, as mentioned,
general multiple inheritance, or, in a different direction, avoiding
headers that the implementation part needs. But it's generally OK.


> Or did I misunderstand something? I think somebody said pure virtual
> interfaces should not have data/member variables. Did I misunderstand it?

As a rule of thumb that works.

But in any given case one should, if possible, do what seems reasonable
for the case at hand, preferably after /informed/ consideration, but in
the worst case just gut feeling, and not blindly follow a rule of thumb.

Following mechanical rules that have worked for others is a nice
default, generally not a bad idea, but if software could be developed
only that way it would have been developed by cheap software robots.

I guess the main advice is to know the /rationale/ of the rules, why
they are recommendations that generally work out OK, so that one can
decide on an informed basis whether to follow them in any given case.

And that's what you're doing now, asking for rationale, so you're fine.


Cheers!,

- Alf

Alf P. Steinbach

unread,
Oct 11, 2018, 7:00:42 PM10/11/18
to
On 12.10.2018 00:57, Alf P. Steinbach wrote:
> [sniop]
> A pure interface will usually not have data members, but it's a feature
> of C++ that it /can/ have data members.

I meant to write, an *interface*.

When it has a data member it's no longer pure.


Cheers!,

- Alf

JiiPee

unread,
Oct 11, 2018, 8:32:56 PM10/11/18
to
On 11/10/2018 23:57, Alf P. Steinbach wrote:
>> Or did I misunderstand something? I think somebody said pure virtual
>> interfaces should not have data/member variables. Did I misunderstand
>> it?
>
> As a rule of thumb that works.


ok, so its a rule of thumb. But in each case not to follow blindly. I
was just thinking about this long time. OK, good to know I was pretty
much on right track (I also thought It can be ok).


>
> But in any given case one should, if possible, do what seems
> reasonable for the case at hand, preferably after /informed/
> consideration, but in the worst case just gut feeling, and not blindly
> follow a rule of thumb.
>
> Following mechanical rules that have worked for others is a nice
> default, generally not a bad idea, but if software could be developed
> only that way it would have been developed by cheap software robots.

>
> I guess the main advice is to know the /rationale/ of the rules, why
> they are recommendations that generally work out OK, so that one can
> decide on an informed basis whether to follow them in any given case.

Yes, and that was clarified here.


JiiPee

unread,
Oct 11, 2018, 8:35:48 PM10/11/18
to
On 11/10/2018 23:27, Stefan Ram wrote:
> There is nothing that speaks against addding
> data to a class with some pure virtual functions, it just
> does not represent the idea of an interface very well anymore.


Sure, and maybe then its not even meant to be an interface.

But was thinking also should this Animal thing be changed so that it is
a pure interface? like somebody said, we could have a AnimalBase which
would be pure. and Animal inherited maybe from it? I was actually
thinking about this also

JiiPee

unread,
Oct 11, 2018, 8:41:46 PM10/11/18
to
On 11/10/2018 23:10, Mr Flibble wrote:
> Perhaps you should make talk() pure virtual instead?


I was thinking if the animal cannot talk then no need to implement this
function. but yes, its not that simple, because maybe for debugging
purposes we want that talk() also for them.

Bo Persson

unread,
Oct 12, 2018, 5:11:15 AM10/12/18
to
On 2018-10-12 02:32, JiiPee wrote:
> On 11/10/2018 23:57, Alf P. Steinbach wrote:
>>> Or did I misunderstand something? I think somebody said pure virtual
>>> interfaces should not have data/member variables. Did I misunderstand
>>> it?
>>
>> As a rule of thumb that works.
>
>
> ok, so its a rule of thumb. But in each case not to follow blindly. I
> was just thinking about this long time. OK, good to know I was pretty
> much on right track (I also thought It can be ok).
>

Some of these rules are derived from the use of other languages, where
an interface just cannot have data members. That makes it easy (for
those languages) to make it a hard rule. Because that is the only
option. :-)

C++ being a multi-paradigm language lets you make your own choices. One
possible choice is to be more pragmatic and not blindly follow the
rules/guidelines when they seem like a bad fit for the case at hand.


Bo Persson

JiiPee

unread,
Oct 12, 2018, 6:37:03 AM10/12/18
to
well said


JiiPee

unread,
Oct 12, 2018, 6:42:13 AM10/12/18
to
On 12/10/2018 10:11, Bo Persson wrote:
>
> C++ being a multi-paradigm language lets you make your own choices.

to be honest, this is why I liked C++, its not so strict to certain
"syntax", but you can do your own things there if you want. (obviously
needs to be careful not to go too far but follow the recommendations).
thats why I also like(d) assembly language: you can do even more tricks
if you want/need :).
And this is why I never really like Basic language (although started
with it): it does not have pointers and is quite limited language. I
wanted "all" :). Currently C++ is best language in that sense - it lets
you to do your own tricks most. (Of course Basic language has also its
place and so on...I understand).

Juha Nieminen

unread,
Oct 13, 2018, 4:19:34 AM10/13/18
to
Mr Flibble <flibbleREM...@i42.co.uk> wrote:
> It is perfectly fine for abstract base classes to have member variables
> unless the class is representing an *interface* in which case the
> idiomatic thing to do is to not have any member variables.

Why shouldn't interfaces have member variables? Just because other
languages don't support them? Why limit yourself, and let yourself
be governed by the limitations of other languages?

Thiago Adams

unread,
Oct 13, 2018, 7:56:16 AM10/13/18
to
On Thursday, October 11, 2018 at 7:04:11 PM UTC-3, JiiPee wrote:
> They say (Bjarne?) that pure virtual class should not have any
> data/member variables. Is this true? I created an example where I think
> member variable seems to belong to the pure virtual base class:
>
> #####################
>
> class Animal
> {
> public:
>     Animal(int age) : age_{age} {
>     }
>     virtual ~Animal() = 0; // make it pure virtual
>     virtual void talk() {
>     }
> private:
>     int age_;
> };
>
> inline Animal::~Animal()
> {
> }
>
> class Dog : public Animal
> {
> public:
>     Dog(int age) : Animal{ age } {
>     }
>     void talk() override {
>         cout << "Meouw";
>     }
> };
>
> ##################
>
> Where is the correct place for age_ assuming that *all animals have
> age*. So lets assume that 100% all animals will have age-variable (they
> need it). But its logical that Animal is pure virtual. So, where should
> age_ belong to if not inside Animal class?

Inheritance causes a LOT of coupling. (Traditional OOP as well)
Using inheritance for implementation(data, algorithm) creates even
more coupling. This is the reason to avoid data member in interfaces
and avoid inheritance using composition.

Because of this coupling the answer for your question
"Where is the correct place for age_ " can be only given
when the software is complete. But even after that,
if you have the perfect balance you will end up with
classes that are hard to reuse because the balance you
did was for the original problem.

Think in a case where you have 10 classes "balanced"
and using some data member from base classes.
Now, software requisites changes and you need to add
a new class that doesn't need to use some of the data
members. So to add one more object you need to reevaluate
all your code and maybe make changes in your existing
classes.

This problem can happen even with interfaces without any
data members. When you add some class and the implementation
of some virtual doesn't make sense this is a sign. The set
of classes and virtual functions is broken and you need
redesign globally.

Interfaces are good when you need to make "plugins".
A new class can be added without have access to
the other classes in an independent way. The interface
contract should be small and very direct.
But it can change, then you need a new interface, let´s say
Interface2 or InterfaceEx.

When I have access to all objects that will implement
some behavior then I use a different approach.


Basically instead of use interfaces I say
"Animal is a pointer to Cat or Dog".
'Dog' or 'Cat' don´t know 'Animal' and this creates
much less coupling. The couple caused is to add
new algorithm but this is done automatically by
the tool.

See
http://thradams.com/web/cprime.html

the polymorphism sample.


Thiago Adams

unread,
Oct 13, 2018, 8:22:33 AM10/13/18
to
On Saturday, October 13, 2018 at 8:56:16 AM UTC-3, Thiago Adams wrote:
...

> Basically instead of use interfaces I say
> "Animal is a pointer to Cat or Dog".
> 'Dog' or 'Cat' don´t know 'Animal' and this creates
> much less coupling. The couple caused is to add
> new algorithm but this is done automatically by
> the tool.
>
> See
> http://thradams.com/web/cprime.html
>
> the polymorphism sample.

I think this feature could be added into C++.

Some syntax to specify the pointer

union Animal = Dog | Cat;

Dog and Cat must have some RTTI.
(today this is only activated by virtuals,
so imagine a virtual destructor)


class Dog {
public:
void talk() {
cout << "auau";
}
};


class Cat {
public:
void talk() {
cout << "Meouw";
}
};


union Animal = Dog | Cat;
Animal* p = new Dog();

p->talk(); //<--------

the p->talk would instantiate the selection function
similarly of template instantiation.

This instantiation checks if Dog and Cat have the talk()
function with the same signature.

The declaration
union Animal = Dog | Cat;
checks if Dog and Cat have rtti.


Non member functions also should work and double dispatch
is possible as well.

union Shape = Box| Circle;

Intercept(pShape1, pShape2); //select instantiation

Selects:
Intercept(Box*, Box*);
Intercept(Box*, Circle*);
etc...







Öö Tiib

unread,
Oct 13, 2018, 9:14:44 AM10/13/18
to
On Saturday, 13 October 2018 15:22:33 UTC+3, Thiago Adams wrote:
> I think this feature could be added into C++.
>
> Some syntax to specify the pointer
>
> union Animal = Dog | Cat;

C++17 already added it as std::variant. Its "visit" can become bit
cryptic when to put wildly different stuff into variant (like examples
often love to do) but on given case it is almost self-explanatory:

#include <iostream>
#include <variant>
#include <vector>

struct Bee {
void talk() {
std::cout << "buzz";
}
};

struct Bear {
void talk() {
std::cout << "growl";
}
};

struct Bird {
void talk() {
std::cout << "chirp";
}
};

using Animal = std::variant<Bee, Bear, Bird>;

int main() {
std::vector<Animal> animals;
animals.emplace_back(Bird{});
animals.emplace_back(Bee{});
animals.emplace_back(Bear{});

auto thingTalk = [](auto&& thing) { thing.talk(); };

for (auto a: animals) {
visit(thingTalk, a);
}
}

Seems to work too:
http://coliru.stacked-crooked.com/a/597d761be7b750cb

It may become even better with concepts or not ... future will show.

Thiago Adams

unread,
Oct 13, 2018, 9:19:54 AM10/13/18
to
Template functions also could be polymorphic

For instance:

class Dog
{
template<class T>
void Eat(T & v) {...}
};

class Cat
{
template<class T>
void Eat(T & v) {...}
};

//let's say this is the syntax to
//create the pointer to cat or dog

union * Animal : Cat , Dog
{
//we can declare the functions here
//and the compiler can check Cat and Dog
//to see if the function is present
template<class T>
void Eat(T & v);
};


Animal * p = new Dog();

p->Eat(1); //instantiation

the instantiation will create a version of Eat
that will make runtime time selection (like switch and cases).
This instantiation will instantiate the dog Eat and cat Eat
as well.













Thiago Adams

unread,
Oct 13, 2018, 9:45:00 AM10/13/18
to
I think the 'tag' that differentiate objects is inside the
std::variant. This is bad for this kind of use.
(I think rust has a similar feature and problem)

My suggestion is only for pointers and the 'tag' is rtti must
be inside the object.
I can say Animal is a pointer to Dog or Cat. It doesn't hold dog
or cat memory . I cannot instantiate an Animal object, just a pointer.

This sample shows some problem (it is not composable and
if it was it was going to have multiple 'tags')


#include <variant>
#include <iostream>
#include <variant>
#include <vector>


struct Bee {
void talk()
{
std::cout << "buzz";
}
void serialize()
{
std::cout << "serialize";
}
};

struct Bear {
void talk()
{
std::cout << "growl";
}
void serialize()
{
std::cout << "serialize";
}
};

struct Bird {
void talk()
{
std::cout << "chirp";
}
void serialize()
{
std::cout << "serialize";
}
};

using Animal = std::variant<Bee, Bear, Bird>;

struct Car {
void serialize()
{
std::cout << "serialize";
}
};

using Items = std::variant<Animal, Car>;


int main()
{
std::vector<Items> items;
items.emplace_back(Bird{});
items.emplace_back(Bee{});
items.emplace_back(Bear{});
items.emplace_back(Car{});

//error C2039: 'serialize': is not a member of
//'std::variant<Bee,Bear,Bird>'

auto f = [](auto&& thing) { thing.serialize(); };

for (auto a : items)
{
visit(f, a);
}
}





Thiago Adams

unread,
Oct 13, 2018, 10:05:45 AM10/13/18
to
This is the way I do today (using my transpiler
http://thradams.com/web/cprime.html
)


struct Bee
{
int tag /*= 1*/;
};

void Bee_talk(struct Bee* p)
{
printf("buzz");
}

void Bee_serialize(struct Bee* p)
{
printf("serialize");
}

struct Bear
{
int tag /* = 2*/;
};

void Bear_talk(struct Bear* p)
{
printf("growl");
}

void Bear_serialize(struct Bear* p)
{
printf("serialize");
}

struct Bird
{
int tag /* = 3*/;
};

void Bird_talk(struct Bird* p)
{
printf("chirp");
}

void Bird_serialize(struct Bird* p)
{
printf("serialize");
}

struct /*Bee | Bear| Bird*/ Animal
{
int tag;
};

//This function is managed by the transpiler
void Animal_talk(struct Animal* p) /*default*/
{
switch (p->tag)
{
case 3:
Bird_talk((struct Bird*)p);
break;
case 2:
Bear_talk((struct Bear*)p);
break;
case 1:
Bee_talk((struct Bee*)p);
break;
default:
break;
}
}

struct Car
{
int tag /*= 4*/;
};

void Car_serialize()
{
printf("serialize");
}


struct /*Animal | Car*/ Item
{
int tag;
};

//This function is managed by the transpiler
void Item_serialize(struct Item* p) /*default*/
{
switch (p->tag)
{
case 3:
Bird_serialize((struct Bird*)p);
break;
case 2:
Bear_serialize((struct Bear*)p);
break;
case 1:
Bee_serialize((struct Bee*)p);
break;
case 4:
Car_serialize((struct Car*)p);
break;
default:
break;
}
}

//Not possible because Car_talk doesn't exists
//void Item_talk(struct Item* p) /*default*/;

int main()
{
}


Having some similar feature in the C++ compiler
also would require cast analysis. static_cast and
dynamic cast could have an especial version for
this kind of object.

The dynamic_cast would allow only objects that
are listed as possible.

I have a name for 'closed polimorphism' where
all classes are know and 'open polimorphism' where
we don´t know all classes that implement some interface
like 'plugins'.
'closed polimorphism' can be implemented in this way and
'open polimorphism' must use interfaces.


Mr Flibble

unread,
Oct 13, 2018, 12:46:31 PM10/13/18
to
Of course there is nothing to stop you adding member variables it just can
no longer be described as an "interface" (idiomatically at least) it would
then be just an abstract base class.

Öö Tiib

unread,
Oct 13, 2018, 1:03:30 PM10/13/18
to
That concern is also already solved since one can certainly use
std::variant<Dog*,Cat*> if they so wish.
The issue here is that std::variant<Animal, Car> is not same as
std::variant<Bee, Bear, Bird, Car> like you seemingly expect.
If there is desire to merge type lists of two variants then that
takes some meta-programming with variadic templates.

Juha Nieminen

unread,
Oct 13, 2018, 1:48:09 PM10/13/18
to
Mr Flibble <flibbleREM...@i42.co.uk> wrote:
> Of course there is nothing to stop you adding member variables it just can
> no longer be described as an "interface" (idiomatically at least) it would
> then be just an abstract base class.

So it works exactly like an interface, and is used exactly like an
interface, and serves the exact same role as an interface, but it
can't be called an interface because it happens to have some member
variable (which might even be private so it isn't even visible from
the outside)?

Mr Flibble

unread,
Oct 13, 2018, 3:39:41 PM10/13/18
to
Correct, it doesn't work exactly like an interface if it has member
variables. Also interface methods cannot have an implementation having
private member variable would serve no purpose.

Bo Persson

unread,
Oct 13, 2018, 4:32:06 PM10/13/18
to
Yes, it apparently violates the "pure" part. :-)

I some other languages we know we have to do the interface part
separately and then perhaps add a default implementation as a separate
layer in a derived class.

C++, being a multi-paradigm language, will let you do it all in one
class. And it even allows you multiple inheritance from several non-pure
base classes.

Heresy or just being practical?


Bo Persson

Juha Nieminen

unread,
Oct 13, 2018, 4:53:04 PM10/13/18
to
Mr Flibble <flibbleREM...@i42.co.uk> wrote:
>> So it works exactly like an interface, and is used exactly like an
>> interface, and serves the exact same role as an interface, but it
>> can't be called an interface because it happens to have some member
>> variable (which might even be private so it isn't even visible from
>> the outside)?
>
> Correct, it doesn't work exactly like an interface if it has member
> variables. Also interface methods cannot have an implementation having
> private member variable would serve no purpose.

What exactly is the advantage of such interfaces, compared to
"interfaces" with function implementations and perhaps even
member variables?

I don't see any practical (or otherwise) difference from the
perspective of a piece of code that wants an object that implements
a given "interface". Why should that code care whether the
implementation is (partially) in the interface class itself or
in a derived class? (After all, usually this kind of code doesn't
even know, nor care, what the actual class of the object behind
that pointer/reference is. So why should it care whether some
function implementation is in the interface or that derived
class? In what situation could it even make that distinction,
even if for some unfathomable reason it would want to care
about the difference?)

JiiPee

unread,
Oct 13, 2018, 7:05:07 PM10/13/18
to
On 13/10/2018 21:31, Bo Persson wrote:
> Yes, it apparently violates the "pure" part.

how about naming them "pure interface" and "interface"? that would give
space for both point of views.

I kinda think if something looks like an interface, acts like an
interface it probably is an interface .

We do the same "class" interface: class is not defined clearly, but we
accept that if it looks roughly like an class we can call it class.

See:

class Dog

{

public:

..

private:

int age;

};

// in the same file:

void printAge(Dog& dog)

{

// get dogs age and print

}

now printAge is not inside class but many see it part of the class
interface (because it is in the same file and delivered with class Dog).
But its not 100% clear whether it should belong there or not....there
surely are many functions which we are not 100% sure if they belong to
this class interface or not.

Mr Flibble

unread,
Oct 13, 2018, 9:14:51 PM10/13/18
to
The term "interface" is already well defined in the C++ sense: it is a
class that only contains pure virtual methods; there already exist terms
for other things that are not exactly this.

The main reason why this definition of the term "interface" pervades is
because it closely resembles Java and C# interfaces however Microsoft
probably first begat the term in the context of C++ when designing COM
many years ago: the 'I' prefix of 'IUnknown' means "interface".

bitrex

unread,
Oct 13, 2018, 11:53:00 PM10/13/18
to
Otherwise pure virtual base classes seem to be a good place to put
member variables for record keeping e.g. object counters that count how
many of a given base type of object are in existence at a time if one
needs that. or to generate unique identifiers/tags for a given object
when its instantiated.

There's no law that you can't combine static and dynamic polymorphism;
you don't always need every base class pointer to be able to be used
call all derived methods a subclass has, sometimes only a a subset need
to be used that way. The rest of the polymorphic interface can be static
done with say the CRTP. AFAIK there's no law that says pure virtual
functions of the abstract base have to necessarily be overriden in the
direct subclass.

so you can have different implementations of FooImpl here for
FooInterface, but even so "optional" can be inlined because it doesn't
need to be called dynamic-polymorphically:

#include <iostream>

class FooBase {
public:
virtual ~FooBase() {}

void required() { required_(); }

protected:
FooBase() = default;

private:
virtual void required_() = 0;
};

template <typename Impl>
class FooInterface : public FooBase {
public:
void required() { static_cast<Impl*>(this)->required_(); }

void optional() { static_cast<Impl*>(this)->optional_(); }
};

class FooImpl : public FooInterface<FooImpl> {
friend class FooInterface<FooImpl>;

protected:
void optional_() { std::cout << "Optional\n"; }

private:
void required_() override { std::cout << "Required\n"; }
};

int main() {
FooBase* base_ptr = new FooImpl();
FooImpl* derived_ptr = new FooImpl();

base_ptr->required();

derived_ptr->required();
derived_ptr->optional();
}

David Brown

unread,
Oct 14, 2018, 6:40:04 AM10/14/18
to
I think the biggest practical issue with "interface with member" classes
is for multiple inheritance. When you have interface classes with only
functions, it's no problem to inherit from several of them. If there
are members, you need virtual inheritance (which is complicated and less
efficient).

Öö Tiib

unread,
Oct 14, 2018, 7:39:31 AM10/14/18
to
The term "interface" is terribly overused in various senses and in all
programming including in C++. Rather typical is (for example) a discussion
what is better "interface" A or B:

auto p1 = geometry::perimeter(circle); // A
auto p2 = circle.perimeter(); // B

JiiPee

unread,
Oct 14, 2018, 8:32:18 AM10/14/18
to
so can you give and example how to change that Animal class not to place
the data there

Thiago Adams

unread,
Oct 14, 2018, 2:07:13 PM10/14/18
to
I think it is better to have the information
about the type inside the object (like vtable does)
instead of having it inside the reference to the object
(like std::variant<Dog*,Cat*> does).

I have used this pattern (tag inside the object) in my parser AST.
I have two data structures pointing to the same object. I have map
that maps string->object and I have a tree to walk and find
declarations.

If I had used something like std::variant<Dog*,Cat*> (have a tag inside)
then I would ended using more memory compared with raw pointers
pointing to the object that have the 'tag' inside.

map<string, std::variant<Dog*,Cat*>>
vector<std::variant<Dog*,Cat*>>

instead of

map<string, Animal*>
vector<Animal*>


So, I think std::variant is not ideal in this case.
I think it was created to be used like tagged unions, like
COM variant.

Using C++ and maybe some techniques used in std::variant
it is possible to create something useful before to have
it inside the language.

I did this sample in 2014
http://thradams.com/dynamic_select.htm

I have dynamic_select and typeid is used.

std::vector<std::unique_ptr<object>> v;
v.emplace_back(new box());
v.emplace_back(new circle());

for (auto& item : v)
{
dynamic_select<box, circle>(item.get(),
[](auto a)
{
draw(a);
});
}


I believe some support from the language would be interesting.

I have considered to use

union Shape
{
Box * pBox;
Circle* pCircle;
};

to say I have a pointer to box or circle.
But to use this data struct the code was very
confusing (like to create an out pointer) and
the chances to create error was too big.


Öö Tiib

unread,
Oct 14, 2018, 3:11:26 PM10/14/18
to
Like every tool variant is also not silver bullet. Sometimes
run-time polymorphism is best through virtual functions, sometimes
it is best using visitor pattern and sometimes it is best using
variants. I still do not understand in what context your idea is better
from those others.

> Using C++ and maybe some techniques used in std::variant
> it is possible to create something useful before to have
> it inside the language.
>
> I did this sample in 2014
> http://thradams.com/dynamic_select.htm
>
> I have dynamic_select and typeid is used.
>
> std::vector<std::unique_ptr<object>> v;
> v.emplace_back(new box());
> v.emplace_back(new circle());
>
> for (auto& item : v)
> {
> dynamic_select<box, circle>(item.get(),
> [](auto a)
> {
> draw(a);
> });
> }
>
>
> I believe some support from the language would be interesting.
>
> I have considered to use
>
> union Shape
> {
> Box * pBox;
> Circle* pCircle;
> };
>
> to say I have a pointer to box or circle.
> But to use this data struct the code was very
> confusing (like to create an out pointer) and
> the chances to create error was too big.

The dynamic dispatch through typeid works only with classes that have
virtual functions. In actual programs such types with virtual functions
seem more rare than other types. Also, if you have to have virtual
functions and have to check typeid through vtable then how it can be
better than just to use the virtual functions?

Thiago Adams

unread,
Oct 14, 2018, 5:44:59 PM10/14/18
to
It gives some benefits of visitor. I have to think more
about the advantages over visitor, but I think it is simpler
and faster.
I am using this pattern (not the C++ version, but the C one,
with int as tag and switch cases) to implement an AST data
structure.

Sometimes we have two or more types of objects inside the same
container. It doesn't mean that these types have something
in common. Maybe the only think they have in common is that
they are used together.
Lets say you have a container of 'car parts'. Them we have
glass and tires. What do they have in common?
In this case, using interfaces we may have IObject and
the container vector<IObject>.
But IObject is two generic. I can have glass or tires, but
I cannot have fruit. If we had ICarPart then the glass or tire
is very coupled with the original problem and is difficult to reuse
and confuse as well.

In the AST, one sample is static_assert it can be in different
positions without any relationship with the other parts.

I will consider this solution in many other cases,
where I am the owner of the types. (I know all types)
I need the RTTI and the virtual destructor was needed to
use unique_ptr in this sample.

The sample is avoiding coupling cause by interfaces.
I am not using this dynamic_select in any program, but I am
using the 'dynamic switch selection' in a C program.



Thiago Adams

unread,
Oct 14, 2018, 6:31:53 PM10/14/18
to
On Sunday, October 14, 2018 at 6:44:59 PM UTC-3, Thiago Adams wrote:
...
> Lets say you have a container of 'car parts'. Them we have
> glass and tires. What do they have in common?
> In this case, using interfaces we may have IObject and
> the container vector<IObject>.
> But IObject is two generic. I can have glass or tires, but
> I cannot have fruit. If we had ICarPart then the glass or tire
> is very coupled with the original problem and is difficult to reuse
> and confuse as well.
>
and just to show how far this can go..
Imagine you have a container of airplane parts,
then you can have glass, tires and wing.

glass, tires can be at car parts or airplane parts, but
wing can be only at airplane parts.
Considering this, then ICarPart stopped to make sense. and here
we go again trying to find sets of interfaces.
If we use IObject this doesn't represents well the possible
objects that the container can have.






Pavel

unread,
Oct 14, 2018, 11:47:15 PM10/14/18
to
JiiPee wrote:
> so can you give and example how to change that Animal class not to place
> the data there
As written, Animal::age_ is inaccessible from outside the class. When
you decide who is to use it (what methods/classes), you will have better
idea where to put it. If you want age to be accessible for every animal
via a public or protected method, you can leave it in Animal. If all you
are interested in for your interface is talking, you should probably
rename your interface to Talker and move age_ to some LivingThing class
-- that don't even need to have virtuals unless there are other
requirements, thus having something like
class Talker {
public:
virtual void talk() const = 0;
...
};
class LivingThingData {
int age_;
public:
int age() const { return age_; }
};
then,
class Cat: public Talker, private LivingThingData { /* assuming you only
need age inside, say, to decide on the pitch for meowing.. */
};

Essentially, analyze business model and separate concerns. I don't think
you can get once-and-for-all "correct" problem decomposition: good model
for a set of requirements may become inadequate as requirements are
added so you have to plan for some rework. The best you can do is to
separate concerns that you know should be separate as of the *current*
set of business requirements.

HTH
-Pavel

Juha Nieminen

unread,
Oct 15, 2018, 2:29:38 AM10/15/18
to
David Brown <david...@hesbynett.no> wrote:
> I think the biggest practical issue with "interface with member" classes
> is for multiple inheritance. When you have interface classes with only
> functions, it's no problem to inherit from several of them. If there
> are members, you need virtual inheritance (which is complicated and less
> efficient).

No, you don't.

You might need virtual inheritance in a case of diamond inheritance.
However, interfaces aren't inherited from a common base class, so
virtual inheritance isn't needed and would do nothing.

Juha Nieminen

unread,
Oct 15, 2018, 2:34:06 AM10/15/18
to
Öö Tiib <oot...@hot.ee> wrote:
> The term "interface" is terribly overused in various senses and in all
> programming including in C++.

It doesn't exactly help that "interface" means in other context simply
the collection of public methods of a class (as in the "public interface"
of the class).

"Interface" in those language is really ill-named. Objective-C uses
the term "protocol" instead, which is probably better. (Of course
"protocol" itself is already in use in other contexts, but those
contexts are different enough that it's justifiable.)

David Brown

unread,
Oct 15, 2018, 2:46:55 AM10/15/18
to
You are right - you only need the virtual inheritance if you are getting
the interface (and therefore the same data member) from more than one
ancestor. For some kinds of interface classes that can be likely, so
you might still end up using virtual inheritance just in case.

vam...@proekspert.ee

unread,
Oct 15, 2018, 8:03:35 AM10/15/18
to
Lets call your solution with some name? Something like
"intrusive tags"? Then it is easier to reason when things have
names. ;) Modern day often only profiler can tell for sure what
has advantage or disadvantage in fastness in concrete usage
situations. Thinking helps a bit but since compilers are quite
clever now it is often wrong. If it is faster then it can be used
as performance optimization. I don't see how it is simpler.

> Sometimes we have two or more types of objects inside the same
> container. It doesn't mean that these types have something
> in common. Maybe the only think they have in common is that
> they are used together.
> Lets say you have a container of 'car parts'. Them we have
> glass and tires. What do they have in common?
> In this case, using interfaces we may have IObject and
> the container vector<IObject>.
> But IObject is two generic. I can have glass or tires, but
> I cannot have fruit. If we had ICarPart then the glass or tire
> is very coupled with the original problem and is difficult to reuse
> and confuse as well.

We can use some problem domain oriented name like
"IPart" for possibly spare parts of different vehicles or
"IConsumable" for stuff that can be possibly eaten by
beings. But we will lose compile-time checking that
"wing" is unacceptable "part" for "car" or "hay" is
unacceptable "food" for "tiger" both with such
interfaces and with your intrusive tagging.
With visitor it is also likely run-time issue, with variant
there is concrete set of types so it is compile-time
issue.

> In the AST, one sample is static_assert it can be in different
> positions without any relationship with the other parts.

Two things are important: if it is easy to unit-test and
if less skilled programmers can take over its maintenance.
I feel that intrusive tagging can be unit-tested OK but it
takes yourself to be in control over it right now.

With AST-like problems I would go with variant anytime,
smaller types directly, bigger, polymorphic and/or recursive
types as (smart)pointer in variant. We can concentrate on
actual features and leave performance questions for later.

Code of std::variant is huge but it is mostly compile-time
meta-programming. The external tag of it is not such a
speed or storage issue and the compile-time type safety
defeats lot of issues with clumsy maintenance.

> I will consider this solution in many other cases,
> where I am the owner of the types. (I know all types)

Yes, I think the weakness is that you have to own the types.
With unknown, future types the options are always limited
to usage of abstract interface classes, clever compile-time
meta-programming, code generators, callbacks, lambdas
and/or std::function. But your intrusive tags can be useful
as performance optimization in implementation details.
Yes, in C it makes lot of sense since every data in C is POD
with certain binary layout. In C++ the intrusive tags are tricky
for two reasons:

1) Same binary location of tags in objects of different types is
hard to achieve. "Standard layout" is rather restrictive trait.
Otherwise it is undefined behavior. Having special purpose
interface to read the tag (like ITagged) would however defeat
most efficiency advantages. Also dynamic (cross?)casting to
what it apparently leads into can raise major violent
controversy (AKA shitstorm) in some collectives.

2) Difference between different tags and equality of tags
to same type in shared library and main program is hard
to achieve automatically. There are no "link-time counters"
or "link-time GUID" in C++. Linking is perhaps mentioned
in less than handful of non-normative comments of
standard. That issue leads either to language extensions
or to code generators.

Thiago Adams

unread,
Oct 15, 2018, 8:48:00 AM10/15/18
to
OK!

>Modern day often only profiler can tell for sure what
> has advantage or disadvantage in fastness in concrete usage
> situations. Thinking helps a bit but since compilers are quite
> clever now it is often wrong. If it is faster then it can be used
> as performance optimization. I don't see how it is simpler.

I could have used pointer to some function or global variable
like vtable as intrusive tag.
Then the linker would give me a value for the tag and I don't
need to manage unique ids for all objects.

But I believe that integers, preferentially sequencial,
can give me the best performance because the compiler will
see the switch case instead of if else if else ..
Also something interesting for performance is when the virtual
function is very small it can be inlined inside the switch.

void Shape_Draw(struct Shape* pShape) /*default*/
{
switch (pShape->id)
{
case 2:
Circle_Draw((struct Circle*)pShape);
//could be inlined
break;
case 1:
Box_Draw((struct Box*)pShape);
break;
default:
break;
}
}


> > Sometimes we have two or more types of objects inside the same
> > container. It doesn't mean that these types have something
> > in common. Maybe the only think they have in common is that
> > they are used together.
> > Lets say you have a container of 'car parts'. Them we have
> > glass and tires. What do they have in common?
> > In this case, using interfaces we may have IObject and
> > the container vector<IObject>.
> > But IObject is two generic. I can have glass or tires, but
> > I cannot have fruit. If we had ICarPart then the glass or tire
> > is very coupled with the original problem and is difficult to reuse
> > and confuse as well.
>
> We can use some problem domain oriented name like
> "IPart" for possibly spare parts of different vehicles or
> "IConsumable" for stuff that can be possibly eaten by
> beings. But we will lose compile-time checking that
> "wing" is unacceptable "part" for "car" or "hay" is
> unacceptable "food" for "tiger" both with such
> interfaces and with your intrusive tagging.
> With visitor it is also likely run-time issue, with variant
> there is concrete set of types so it is compile-time
> issue.

Exactly. I realized the collection makes the problem worst
because they create this problem of have a common base interface.

Some hierarchies like iostreams doesn't need to worry about
this because this problem would be difficult to happen
in that scenario.

In other software I did an interface to represent data source
like IDataSource.
Then I could add more data sources without to have to change
the code that uses data source.
In more than one situation I had to add new function in the
IDataSource interface and implement this function in all types.
But some data sources were not able to implement that function
and I had to return an error.

I think this scenario was a good use case for interfaces. But
I am not sure I would try the 'intrusive tag' next time.
The advantage is that we can implement some algorithm selectively.
For instance, a need a new function F that only works for some
types. Then I can implement this diferences (return an error for instance)
in my switch cases without to change the classes that implement
the interface the diferences will be clear at the algorithm itself.

The disadvantage of having the switch is manually update.
This is why I think we could have support from the language
and do something like an template instantiation.

I already did this in my transpiler.
http://thradams.com/web/cprime.html
See polimorphism sample. The switch is edited by the compiler.
In C++ we can have a base class like TaggedObject.
This doesn't create coupling because the object is very
simple.
0 new messages