I need to store references to instances of derived classes in C++. I considered using a vector of shared_ptrs to the base class (for it needs to hold different types of derived classes), however, it's important that the container holds the original pointers, which is not the case with vectors (or other stl containers), if I'm not mistaken. Is there a way to do this in native C++, or do I have to use special containers like Boost's ptr_vector?
EDIT: This is my test code:
class Foo
{
public:
Foo() {}
virtual ~Foo() {}
virtual void set_x(int i) = 0;
};
class Bar : public Foo
{
public:
Bar() {}
void set_x(int i)
{
this->x = i;
}
int x;
};
int main()
{
Bar bar;
// ptr
std::cout << &bar << "\n";
std::vector<Foo*> foos;
foos.push_back(&bar);
// different ptr value
std::cout << &foos[0] << "\n";
foos[0]->set_x(1);
// however, changes are made
std::cout << bar.x;
return 0;
}
Thanks in advance,
jena
In your example above, what you are printing out is the address of the pointer not the value of the pointer.
Instead of:
// different ptr value
std::cout << &foos[0] << "\n";
Do
// different ptr value
std::cout << foos[0] << "\n";
Aside from that your vector<Foo*> will work just fine.
You can create a std::vector<foo*>, which will hold any pointers to foo that you hand to it. It won't make any attempt to delete those pointers on destruction, which may or may not be what you want, but it will hold exactly the values you pass in.
You can also create an std::vector< shared_ptr<foo> >, which will hold pointers that will be released once there are no dangling copies of the shared_ptr floating around. Those will also hold the "original" foo* you passed in; you can get it again by using the shared_ptr::get() method.
The only time you wouldn't see exactly the same pointer as your derived object is if you're using multiple inheritance of classes, and your base classes include data. Because a foo* would end up, in that case, pointing to the "foo" part of the data, which wouldn't necessarily be at the "root" of the object.
If you use shared_ptr as your container member, the pointer in each member will retain access to the original object instance. You can get a copy of a shared_ptr at any point after container housekeeping, but the original object will still be its target.
For a simpler solution you might use boost::ptr_vector, provided none of your pointers occur twice in the container - that scenario would introduce tricky resource management complexity and point you back to shared_ptr.
Related
I am struggling to understand the difference in behaviour of a raw pointer and a unique_ptr. I have class A with a variable x and class B with a pointer to an instance of A:
class A
{
public:
int x;
};
A::A(int y) : x(y)
{
}
class B
{
public:
B(A &);
A *p;
};
B::B(A &a)
{
p = &a;
}
This behaves as I would expect:
int main()
{
A a(2);
B b(a);
cout << a.x << " " << b.p->x << endl;
a.x = 4;
cout << a.x << " " << b.p->x << endl;
}
gives
2 2
4 4
Changing the raw pointer to a std::unique_ptr gives a different result:
class A
{
public:
int x;
};
A::A(int y) : x(y)
{
}
class B
{
public:
B(A &);
std::unique_ptr<A> p;
};
B::B(A &a)
{
p = std::make_unique<A>(a);
}
gives
2 2
4 2
Have I fundamentally misunderstood something about unique_ptrs?
make_unique creates a fresh object, one that that unique_pt has exclusive access to. So in the second example you have two objects, not one and when you set change the value of a.x in the first object it doesn't effect the other object held by the unique_ptr.
A unique pointer needs to own whatever it points to. Your code can be made to work - just substituting unique_ptr type and leaving everything else unchanged (no make_unique). But it will have undefined behavior, since you’ll create a unique pointer to an object that is owned elsewhere.
To compare apples to apples, the raw pointer code should read p=new A(a);. That’s what make_unique does.
Try reading the following expression from the smart pointer version:
std::make_unique<A>(a);
"make a unique A from a" (no mention of pointers!)
The result is a unique_ptr, but when reading the expression, read it as making (an object whose type is) the template parameter. The function parameters are parameters to the constructor. In this case, you are making an A object from an A object, which pulls in the copy constructor.
Once you understand that the smart pointer version is making a new A object (and your raw pointer version does not), your results should make sense.
The "unique" in "unique A" might be tricky to understand. Think of it as an object that no one else can lay claim to. It might be a copy of another object, but, taking the role of the unique_ptr, it is your copy, your responsibility to clean up after, and no one else's. Your preciousss, which you will not share (c.f. std::make_shared).
Note that a local variable (like the a in the main function) is the responsibility of the compiler, so it is ineligible to be the object to which a unique_ptr points (or any smart pointer, for that matter).
Using raw pointers, this can be achieved like so:
#include <iostream>
#include <string>
#include <map>
using namespace std;
class base {
public:
virtual ~base(){}
};
class derived1 : public base {
public:
virtual ~derived1(){}
derived1(const int& other) : val(other) {}
int val;
};
class derived2 : public base {
public:
virtual ~derived2(){}
derived2(const float& other) : val(other) {}
float val;
};
int main()
{
derived1 dataA = 4;
derived2 dataB = 3.0f;
std::map<std::string, base*> mapOfPointersToData; //Needs to be a pointer because it can be of type deribed1 or derived2. Cant be smart as pointing to stack memory
mapOfPointersToData["dataA"] = &dataA;
mapOfPointersToData["dataB"] = &dataB;
base* result = mapOfPointersToData["dataA"];
std::cout << dynamic_cast<derived1*>(result)->val << std::endl;
return 0;
}
This works, but I have been told that we should avoid the use of raw pointers in favour of smart pointers or references.
The objects can never be null, so it makes sense to use a reference. We can get around the problem that the map can only store one data type by using a variant:
int main()
{
derived1 dataA = 4;
derived2 dataB = 3.0f;
std::map<std::string, std::variant<derived1, derived2>&> mapOfPointersToData; //Cant be constructed
mapOfPointersToData["dataA"] = dataA;
mapOfPointersToData["dataB"] = dataB;
auto result = mapOfPointersToData["dataA"];
std::cout << std::get<derived1>(result).val << std::endl;
return 0;
}
but this gives the error
error: value-initialization of reference type ‘std::variant<derived1, derived2>&
So what is the best way to store references to stack data that can be of different types?
This works, but I have been told that we should avoid the use of raw pointers in favour of smart pointers or references.
Raw pointers are only discouraged when they are responsible for owning what they point to (essentially, if you have to remember to delete or otherwise release them). Here, the objects have automatic storage duration and the pointers aren't responsible for cleaning them up. This is a reasonable use case for raw pointers.
The objects can never be null, so it makes sense to use a reference.
Objects in your map might be nullptr if you use operator[] to search the map. That operator adds any element it fails to find and value initializes them (to nullptr for pointers). You can use find instead if you don't want to bloat your map with empty pointers.
So what is the best way to store references to stack data, that can be of different types?
You might be looking for std::reference_wrapper which wraps references in an object that is suitable for containers. But using base* seems like a fine solution already, because your map doesn't own the objects it ultimately points to.
If your objective is to avoid raw pointers in favour of smart pointer, this is what I would do:
int main()
{
std::map<std::string, std::unique_ptr<base>> mapOfPointersToData;
mapOfPointersToData["dataA"] = std::make_unique<derived1>(4);
mapOfPointersToData["dataB"] = std::make_unique<derived2>(3.0f);
auto& result = mapOfPointersToData["dataA"];
std::cout << dynamic_cast<derived1*>(result.get())->val << std::endl;
return 0;
}
Note that I put the object creation into the heap using make_unique<>() calls to highlight the ownership by smart pointer.
So say I have an object, say A, like this
class A {
private:
int x;
public:
A(){};
~A(){};
void sayA() {
std::cout << "A" << std::endl;
}
};
Now If I have a vector of pointers to A
std::vector<A *> As;
can I push back new instances of A to the vector while at the same time accessing the sayA method at the same time?
Like this
As.push_back(new A()->sayA());
Just to point out in this case A is not an Object I created, It's part of a graphics library
You can (ab)use emplace_back in C++17:
As.emplace_back(new A())->sayA();
but really, IMO this is more clear:
As.push_back(new A());
As.back()->sayA();
You could let sayA return a pointer to the instance. Like this:
auto sayA() {
std::cout << "A" << std::endl;
return this;
}
and then insert the instance as follows.
As.push_back((new A())->sayA());
But apart from making it work, this is awkward and hard to read. Consider using a vector of smart pointers and insert first, then call methods. If sayA is part of object construction, call it from A's constructor.
Basically yes, if you make sure that sayA() will return this, since push_back() will expect pointer to A, not void which is current return type of sayA(). And if you return any other pointer to A, then you will leak you newly allocated object since there will be no way to delete it.
BUT
Why you want to have vector of pointers to A and have headache of deleting them later if you can have just std::vector<A> As; and then you can call sayA() as other answers suggested.
#include <iostream>
#include <memory>
class Base
{
public:
virtual void foo() = 0;
};
class Derived : public Base
{
public:
void foo() override { std::cout << "Derived" << std::endl; }
};
class Concrete
{
public:
void Bar() { std::cout << "concrete" << std::endl; }
};
int main()
{
std::unique_ptr<Concrete> ConcretePtr = nullptr;
ConcretePtr->Bar();
std::unique_ptr<Base> BasePtr;
BasePtr->foo();
return 0;
}
I assume declaring a unique_ptr to a concrete type Concrete, allocates memory for an object of type Concrete and the unique_ptr starts pointing to it. Is my assumption/understanding correct ? I ask because ConcretePtr->Bar(); prints "concrete" to the console. But, if I make a unique pointer to an interface Base, it does not know the exact type of object that I need and does not allocate/acquire resources in the memory.
This fails at BasePtr->foo(); with BasePtr._Mypair._Myval2 was nullptr.
Why does the first declaration std::unique_ptr<Concrete> ConcretePtr = nullptr; allocate an object by itself ? what if I did not want it to be pointing to some real object at that very line of code, but wanted only a smart pointer ?
Now if I change the declaration to be std::unique_ptr<Concrete> ConcretePtr; and the Concrete type to be the following,
class Concrete
{
int ConcreteNum;
public:
void Bar()
{
std::cout << "concrete" << std::endl;
ConcreteNum = 38;
std::cout << ConcreteNum << std::endl;
}
};
it fails at ConcreteNum = 38; complaining that this was nullptr; if this this was nullptr then why and how did the earlier call (where Concrete did not have any state ConcreteNum) to Bar work ?
Moreover why does it not fail at ConcretePtr->Bar(); (this -> requires a concrete object, does it not ? what was this here ?) but inside Bar, in that assignment ?
I see the same issue with std::shared_ptr as well. I'm not very sure of the difference between declaration, initialization & assignment. Please help me understand.
I'm using MSVC.
The unique_ptr models a pointer. That is, it's an object that points to another object.
Initialising the unique_ptr with nullptr creates it in the state where it is not pointing to or owning another object.
It's like saying Concrete* p = nullptr.
Initialise it in one of these ways:
std::unique_ptr<Concrete> p{new Concrete()};
or
std::unique_ptr<Concrete> p; // = nullptr is implied.
p.reset(new Concrete());
or, better:
std::unique_ptr<Concrete> p = std::make_unique<Concrete>();
or simply:
auto p = std::make_unique<Concrete>();
But be careful in this case if you really want to be pointing to the Base interface:
std::unique_ptr<Base> p = std::make_unique<Derived>();
or
std::unique_ptr<Base> p = nullptr;
p = std::make_unique<Derived>(); // assignment from rvalue ref of compatible unique_ptr.
std::unique_ptr<Concrete> ConcretePtr = nullptr;
I assume declaring a unique_ptr to a concrete type Concrete, allocates memory for an object of type Concrete and the unique_ptr starts pointing to it. Is my assumption/understanding correct ?
Well, you can trivially check. Write a default constructor for Concrete that prints something out so you can tell when an instance is created. Run the smallest possible program (just the line above in main). Did you see the expected output?
You should be checking this stuff before asking a question (and probably after reading the documentation), but to save you time: no, that line doesn't construct an object of type Concrete.
You can also check explicitly whether unique_ptr is managing an object, with
if (!ConcretePtr) {
std::cout << "ConcretePtr doesn't point to anything\n";
} else {
std::cout << "ConcretePtr owns an object\n";
}
This check is also trivial, and you could easily do it before asking a question.
I ask because ConcretePtr->Bar(); prints "concrete" to the console
This is a bad test because if the pointer is a nullptr, it's undefined behaviour. If you care whether the pointer is a nullptr, you should check that explicitly before dereferencing it, as above.
To demonstrate why this test is confusing you (and you should use the ones above in preference), consider a likely implementation of non-virtual member functions (recall they get an implicit this pointer):
// void Concrete::Bar() implemented as
void Concrete_Bar(Concrete *this)
// and ConcretePtr->Bar() implemented as
Concrete_Bar(ConcretePtr.get());
so, you just passed a nullptr to a function that ignores its only parameter, and you never tested the thing you thought you did.
std::shared_ptr has a nifty templated constructor that automagically creates the right deleter for its given type (constructor #2 in that link).
Until just now, I (erroneously) thought std::unique_ptr had a similar constructor, but when I ran the following code:
#include <memory>
#include <iostream>
// Notice nothing is virtual
struct Foo
{
~Foo() { std::cout << "Foo\n"; }
};
struct Bar : public Foo
{
~Bar() { std::cout << "Bar\n"; }
};
int main()
{
{
std::cout << "shared_ptr:\n";
std::shared_ptr<Foo> p(new Bar()); // prints Bar Foo
}
{
std::cout << "unique_ptr:\n";
std::unique_ptr<Foo> p(new Bar()); // prints Foo
}
}
I was surprised to learn that unique_ptr doesn't call Bar's destructor.
What's a clean, simple, and correct way to create a unique_ptr that has the correct deleter for its given pointer? Especially if I want to store a whole list of these (i.e. std::vector<std::unique_ptr<Foo>>), which means that they all must have a heterogeneous type?
(pardon the poor title; feel free to suggest a better one)
You should make the destructor of Foo virtual. That is good practice regardless of whether you use unique_ptr or not. That will also take care the problem that you are dealing with.
Here's one way:
{
std::cout << "unique_ptr<Bar, void(void*)>:\n";
std::unique_ptr<Foo, void(*)(void*)> p(
new Bar(), [](void*p) -> void { delete static_cast<Bar*>( p ); }
); // prints Bar Foo
}
A main problem with this approach is that unique_ptr supports conversion to logical "pointer to base class", but that the standard does not guarantee that conversion to void* will then yield the same address. In practice that's only a problem if the base class is non-polymorphic while the derived class is polymorphic, introducing a vtable ptr and thus possibly changing the memory layout a bit. But in that possible-but-not-likely situation the cast back in the deleter would yield an incorrect pointer value, and bang.
So, the above is not formally safe with respect to such conversions.
To do roughly the same as a shared_ptr does (shared_ptr supports conversions to logical pointer-to-base), you would need to store also the original void* pointer, along with the deleter.
In general, when you control the the topmost base class, make its destructor virtual.
That takes care of everything.