mercredi 6 février 2019

What strategy should be used when pointing to stack allocated objects?

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 diplaying 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 instance of struct Foo on the stack

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 manipulation the value.

So far the pointer feels like the best option.


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?

Aucun commentaire:

Enregistrer un commentaire