简体   繁体   中英

Inserting derived class instances to std::set

Consider the following environment:

Ah

class A {
public:
A(double);
virtual bool operator==(const A&) const {return FALSE;};
virtual bool operator<(const A&) const {return FALSE;};
double[] values;
int id;
}

Bh

class B : public A {
B(double);
bool operator==(const A&) const ;
bool operator<(const A&) const;
}

C.cpp

std::set<A> myset;
for (unsigned int i = 0; i < 10; i++) {
  B tempElement = B((double)i);
  myset.insert(tempElement);
  std::cout << myset.size() << std::flush;
}

I want to add 10 elements to my new set, but the output of the last line in C.cpp (1111111111) tells me, that there's always only one element present in the set. Right after the loop is left, the B destructor is called. How can I prevent that the object is deleted and that it is inserted in the set as demanded? Do I have to implement a special copy constructor or what I am doing wrong?

EDIT: concerning object slicing: assume, that there are no new members in the extending class. Only the way the operators are defined are different...

You've got a problem with object slicing here. A std::set saves copies of the objects you insert, and the copy constructor of A constructs objects of type A . So the set effectively stores only the A part of the first B object you insert. You'd need something like boost::ptr_set to store derived classes' instances.

I'm not sure why exactly the insert is not happening (too little code), but I assume that any B object is equal to the sliced B object.

The problem you are facing is dual:

  • You are not inserting B s in the set, you are inserting A s (this is the slicing issue)
  • operator< is not correctly defined for A

I'll skip the object slicing issue, it's well documented. Had you used pure virtual functions (instead of meaningless definitions) your code would not have compiled, preventing the issue.

Now, mathematically, whenever an operator< is defined, it should define:

Here, your "dummy" definition of operator< fails to be an antisymmetric relation, and thus algorithms (such as set insertion) which naively expect this to hold... suddenly find themselves giving incoherent results :

On two occasions I have been asked, — "Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?" ... I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.

Charles Babbage , Passages from the Life of a Philosopher

I would also note that the dummy definition of operator== fails to be an equivalence relation (namely a == a does not hold), however == is not used for set, so it is not the cause of the issue.

I strongly urge you to avoid definining dummy behavior like such. It can only lead to confusing errors. Instead, you should make such virtual methods pure (so that those issues are detected during compilation), or at the very least throw an exception.

This isn't the usual case of object slicing if, as you say, the derived class doesn't have any extra members.

Consider, however, how the vtable is initialised: you're passing in a reference to your instance of B ; this will be implicitly cast to A const & so the set can copy-construct an A in place. This A will be an A : there is no way it will copy the vtable for B , so it won't call B s overidden operators.

See, for example, the prototype pattern which uses a virtual clone method to allow virtual copying. Copy constructors certainly don't provide this behaviour.

As for the solution: store pointers, like everyone else has said.

Ok, so i'm quite new in c++ but as I understand the problem: 1) calling destructor after leaving the loop is te result of destroying variable that exists only within the loop. All automate variables has a lifetime of execution of the block that they are defined in. In another words, when you start the loop the object is created and the memory is allocated on the stack, when you leave the loop the memory is released and so the destructor is called. To avoid this effect you should use poiters and dynamic memory allocation instead. 2) i think that 1) results in issues with adding element to the set

In general i think that you make a mistake trying to dynamically create objects using nondinamic memory allocation.

Please correct me if i'm wrong.

The main problem is with operator< . The definition you used, which always returns FALSE, effectively tells the set that all instances of A are equal to each other. As a result, only the first A is accepted into the set. You should change your A::operator< to

virtual bool operator<(const A&a) const {return this->id < a.id;};

or

virtual bool operator<(const A&a) const {return this->values[0] < a.values[0];};

or something like that. What are the constructors of A and B, and how are id and values initialized?

You have other potential problems, such as the slicing mentioned by others. But operator< is the main problem at the moment.

You are wrong in thinking you are not slicing, because you are still creating an instance of an A, which means that the base-class member of operator< and operator== is called.

std::set regards two elements as equal if x

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM