2

I am developing a relatively large library (~13000 lines) as a personal utility library. It uses exclusively STL containers and smart pointers for memory management - but now I find myself in a situation where I could see going for normal pointers due to an apparent lack of an obvious solution in STL. I would like to maintain a modern C++ style, though.

Here's my mcve:

Struct Foo - a int-wrapper struct displaying which member functions were called.

struct Foo {
    Foo(int x) {
        this->x = x;
        std::cout << "constructor\n";
    }
    Foo(const Foo& other) {
        this->x = other.x;
        std::cout << "copy constructor\n";
    }
    ~Foo() {
        std::cout << "destructor\n";
    }

    int x;
};

Looking at main I create an automatic instance of struct Foo:

int main() {
    Foo foo{ 0 };
    std::cout << "\nfoo.x : " << foo.x << "\n\n";    
}

output >

constructor

foo.x : 0

destructor

Easy as that. - now if I want to point to foo and manipulate its content.


Smart pointers like std::unique_ptr or std::shared_ptr won't achieve this effect (apparently!). Using std::make_unique<T>() (/ std::make_shared<T>()) will dynamically allocate memory and only copy the value:

Foo foo{ 0 };
std::unique_ptr<Foo> ptr = std::make_unique<Foo>(foo);

ptr->x = 2;

std::cout << "\nptr->x : " << ptr->x << '\n';
std::cout << "foo.x  : " << foo.x << "\n\n";

output >

constructor
copy constructor

ptr->x : 2
foo.x  : 0 // didn't change  

destructor
destructor

So that's not working. However, their void reset(pointer _Ptr = pointer()) noexcept member function allows to directly assign a pointer:

std::unique_ptr<Foo> ptr;
Foo foo{ 0 };

ptr.reset(&foo);

ptr->x = 2;

std::cout << "\nptr->x : " << ptr->x << '\n';
std::cout << "foo.x  : " << foo.x << "\n\n";

output >

constructor

ptr->x : 2
foo.x  : 2

destructor
destructor //crash

But crash! foo get's deconstructed normally and than std::shared_ptr also wants to deconstruct its already deconstructed dereferenced value.

HEAP[main.exe]: Invalid address specified to RtlValidateHeap(...)


Using a const pointer:

Foo foo{ 0 };
Foo* const ptr = &foo;

ptr->x = 2;

std::cout << "\nptr->x : " << ptr->x << '\n';
std::cout << "foo.x  : " << foo.x << "\n\n";

output >

constructor

ptr->x : 2
foo.x  : 2

deconstructor
  • 1 allocation, 1 deallocation.
  • no copying.
  • actually manipulating the value.

So far the pointer feels like the best option.


I am just looking for a way to point to automatically allocated objects. So far it seems surprisingly difficult without going back to plain pointers.

I provided one solution by myself - which is using /*const*/ std::reference_wrapper<T>. (feel free to correct me about this solution)

But I am not sure what's the best strategy. The pointer? the std::reference_wrapper maybe? Did I made a mistake with using std::shared_ptr / std::unique_ptr?

Is there a better, more straightforward STL class?

Stack Danny
  • 7,754
  • 2
  • 26
  • 55

3 Answers3

4

Hedging my bets a little on this one, as your goal is unclear.


If you want to pass resources around strongly, in a manner that makes ownership and lifetime clear, then a smart pointer is the appropriate way to do that. The standard library already provides them, so just use them.

You've discovered already that this is largely incompatible with objects of automatic storage duration (what you've called "on the stack"). You can work around it with a custom no-op deleter, but honestly why? You're just making life way too complicated for yourself. When you create objects in this manner, the ownership and lifetime are chosen for you, and easy to get wrong (dangling pointers, folks!).

Just have std::make_unique or std::make_shared dynamically allocate your Foos for you, and use the resulting smart pointer in the usual manner.


However, obviously we cannot see your design, nor do we know much about it.

If all you need to do is pass references to objects to functions, which will do a thing then return, then simply do that!

void bar(const Foo& foo)
{
    // do stuff
}

int main()
{
   Foo foo;
   bar(foo);
}
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • +1 Yes I could just pass it through, but that would add a lot of Arguments to all functions and I would rather save the pointer somehow. In the first paragraph you're saying that I should in fact allocate everything dynamically. So in the mcve that `foo` would be dynamically allocated and `ptr` would just point to the resource? – Stack Danny Feb 06 '19 at 12:08
  • 4
    @StackDanny I honestly can't make a firm recommendation without knowing _what you're trying to do_. "but that would add a lot of Arguments to all functions" Why? What sort of arguments? How is a reference "more arguments" than a pointer? Again, unclear. – Lightness Races in Orbit Feb 06 '19 at 12:10
  • Also having all your objects on the stack limits the number of them. I hope you don't expect to store MBs of data on the stack. – Matthieu Brucher Feb 06 '19 at 12:14
  • @LightnessRacesinOrbit well I have multiple objects that need to be passes through. And that's a graphical pipeline, so alot of functions in many loops. In my example the "application" class controls the states of the program and shares it's resources with them. So each loop call & update call & draw call: need to be given as arguments. which feels ugly. – Stack Danny Feb 06 '19 at 12:16
  • What does that have to do with whether you pass pointers or references, or allocate dynamically? – Lightness Races in Orbit Feb 06 '19 at 12:24
  • I hope I can clarify a bit - I am not passing anything through arguments right now, each class a pointer to these objects. But the *initial* objects (the superclass that creates these objects), are also dynamically allocated. So I want to make these initial objects automatic and go `child_class_resource_ptr = &automatic_resource` instead of `child_class_resource_ptr = dynamic_resource_ptr`. So far my impressiong is that it doesn't really matter (but I've seen also the other arguments) – Stack Danny Feb 06 '19 at 12:35
  • 1
    Yeah, sorry, not getting it at all. There seem to be rationales and constraints and arguments missing from your descriptions. Maybe it's just me. Hopefully someone else can help you better. – Lightness Races in Orbit Feb 06 '19 at 12:37
  • I just think that changing from using pointers to passing by reference would completely turn over every design aspect of my classes. But in the end these additional informations (about my library) I doubt are neccessary so I didn't mention them. If I find a suitable solution to the mcve it will work for my complicated real world case. – Stack Danny Feb 06 '19 at 13:06
  • If your objects have automatic storage duration then switching from pointers to references shouldn't change anything at all. What "turn over" aspect do you envisage? I think you need to give some examples - your MCVE should demonstrate the problem – Lightness Races in Orbit Feb 06 '19 at 13:12
3

But I am not sure what's the best strategy. The pointer?

Possibly. If you need to point at an object, then pointer can be a good choice.

Reference might be another:

Foo foo{ 0 };
Foo& ref = foo;
ref = 2;

If a reference is sufficient, then it's usually preferable to a pointer.

Did I made a mistake with using std::shared_ptr / std::unique_ptr?

Yes. You took ownership of an automatic variable. That's a no-no.

In the first example you made a copy of an object, expecting modification of the copy to affect the original.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thanks, I knew there was something wrong with my smart pointers. And yes right now it feels like the right thing to do is just using pointers. – Stack Danny Feb 06 '19 at 12:21
0

std::reference_wrapper:

Foo foo{ 0 };
std::reference_wrapper<Foo> ref = foo;

ref.get().x = 2;

std::cout << "\nref.x : " << ref.get().x << '\n';
std::cout << "foo.x : " << foo.x << "\n\n";

output >

constructor

ref.x : 2
foo.x : 2

deconstructor

Which behaves just like a pointer. Well, looking at it's implementation it is a pointer:

template<class _Ty>
    class reference_wrapper
        : public _Weak_types<_Ty>::type
    {
public:
    /*...*/
private:
    _Ty * _Ptr; //<---
    };

For a pointer const effect (Foo* const) it can be declared as a const std::reference_wrapper<Foo> - disallowing any change of its pointer address.

Foo foo{ 0 };
const std::reference_wrapper<Foo> ref = foo;
Foo foo2{ 2 };
ref = foo2; //error

Error C2678 binary '=': no operator found which takes a left-hand operand of type 'const std::reference_wrapper' (or there is no acceptable conversion)

Stack Danny
  • 7,754
  • 2
  • 26
  • 55