2

I have a case that I am not sure if I should use unique_ptr or pass Object by values.

Say that I have class A which has a vector Of class B and Class C has a vector of class B as well. Every time I am adding a B Object to Vector in class C it should be Removed from vector of Class C and vice versa. When Object C is destroyed all the objects in B Vector class should be add to B vector in class A

class B {
public:
    B();
    virtual ~B();
};

class A {
    C & c;
    std::vector<B> bs;
public:
    A(C & c ): c(c){};
    virtual ~A();
    void add(B b){
        bs.push_back(b);
        c.remove(b);
    }
    void remove(B b){
        bs.erase(std::remove(bs.begin(), bs.end(), b), bs.end());
    }
};

class C {
public:
     A & a;
    std::vector<B> bs;
    C(A & a): a(a) {

    };
    virtual ~C(){
        for (B b : bs) {
                a.add(b);
                remove(b);
            }
    }

    void add(B b){
        bs.push_back(b);
        a.remove(b);
    }
    void remove(B b){
        bs.erase(std::remove(bs.begin(), bs.end(), b), bs.end());
    }
};

My questions:

  1. Is it better to use pointers in this case? I should always have a unique Object for B! In other words if content of two b objects are different they are still different because different address in memory.
  2. I want to write this code with help of smart_pointers in C++ 11! Which type is better shared_ptr or unique_ptr? A B object is never owned by two objects and it has always one owner so I guess unique_ptr is better but I am not sure.
  3. How Can I write the code above using unique_ptr?
Govan
  • 2,079
  • 4
  • 31
  • 53
  • 1
    Most of the time you should not think of the new smart pointers as pointers in the same way as normal non-smart pointers. Instead you should look at them as resource ownership. Should a resource have only a single owner at a time (`std::unique_ptr`) or can a resource have multiple simultaneous owners (`std::shared_ptr`). – Some programmer dude Oct 17 '15 at 14:27
  • You should use a `std::unique_ptr` as it sounds from your requirements. – πάντα ῥεῖ Oct 17 '15 at 14:28
  • 1
    always prefer unique_ptr for your choice of smart pointers – Anwesha Oct 17 '15 at 14:35
  • plz ask ur question in a clearer manner, what do u actually want to do – DevInd Oct 17 '15 at 14:40
  • @CPPNITR I want to write above code using unique_ptr instead of B In classes A and C – Govan Oct 17 '15 at 14:42
  • @CppNITR plz don't use l337 speech! – πάντα ῥεῖ Oct 17 '15 at 14:45
  • @Govan As mentioned you're on the right track IMHO. Can you clarify which particular problem(s) you are stuck with changing to `std::vector>`? – πάντα ῥεῖ Oct 17 '15 at 14:50
  • @πάνταῥεῖ my problem is void remove(B b){ bs.erase(std::remove(bs.begin(), bs.end(), b), bs.end()); } method. How to write that using unique_ptr. When I am removing nique_ptr and add method. Same as Add and destructor – Govan Oct 17 '15 at 14:59
  • @Govan What ever it is, edit your question to clarify! – πάντα ῥεῖ Oct 17 '15 at 15:00
  • You can only have one unique_ptr per object instance. With the current code this rule is violated in several places which leads to compile errors as unique_ptr won't let you make copies (and don't try to trick it as it will result in runtime errors anyway as objects will be deleted to early). Maybe it is possible to not violate any of these cases using std::move and std::forward but I think you are much better of using std::shared_ptr in this case. – Eelke Oct 17 '15 at 15:46
  • Can the vectors contain duplicates? Your add functions only add one element to the vector but delete all matching elements from the other vector. – Chris Drew Oct 17 '15 at 21:47
  • Can you provide example usage code? – Chris Drew Oct 17 '15 at 22:09

2 Answers2

1
  1. If copy-constructing B is costly, then (smart)pointers are probably a good idea (redesigning the application logic might be another solution),

  2. If I understood correctly, a given B instance is always manipulated by a single owner (either A or C). std::unique_ptr is therefore a reasonable choice,

  3. Try the following implementation. I haven't compiled it but I think you'll get the general idea :)

.

class B {
public:
    B();
    virtual ~B();
};

class A {
    C & c;
    std::vector<std::unique_ptr<B>> bs;
public:
    A(C & c ): c(c){};
    virtual ~A();
    // http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/
    // (c) Passing unique_ptr by value means "sink."
    void add(std::unique_ptr<B> b){
        c.remove(b);               // release the poiner from the other container
        bs.emplace_back(b.get());  // emplace the pointer in the new one
        b.release();               // emplacement successful. release the pointer
    }
    // http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/
    // (d) Passing unique_ptr by reference is for in/out unique_ptr parameters.
    void remove(std::unique_ptr<B>& b){
        // @todo check that ther returned pointer is != bs.end()
        std::find(bs.begin(), bs.end(), b)->release();             // first release the pointer
        bs.erase(std::remove(bs.begin(), bs.end(), b), bs.end());  // then destroy its owner
    }
};

class C {
public:
    A & a;
    std::vector<std::unique_ptr<B>> bs;
    C(A & a): a(a) {

    };
    virtual ~C(){
        for (auto&& b : bs) {
            a.add(b);
            // a is going to call this->remove()...
            // unless calling this->remove() from "a"
            // while this is being destroyed is Undefined Behavior (tm)
            // I'm not sure :)
        }
    }
    // http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/
    // (c) Passing unique_ptr by value means "sink."
    void add(std::unique_ptr<B> b){
        c.remove(b);               // release the poiner from the other container
        bs.emplace_back(b.get());  // emplace the pointer in the new one
        b.release();               // emplacement successful. release the pointer
    }
    // http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/
    // (d) Passing unique_ptr by reference is for in/out unique_ptr parameters.
    void remove(std::unique_ptr<B>& b){
        // @todo check that ther returned pointer is != bs.end()
        std::find(bs.begin(), bs.end(), b)->release();             // first release the pointer
        bs.erase(std::remove(bs.begin(), bs.end(), b), bs.end());  // then destroy its owner
    }
};
maddouri
  • 3,737
  • 5
  • 29
  • 51
  • may I ask why are you using emplace_back instead of push_back – Govan Oct 17 '15 at 18:59
  • It's [a little bit more efficient that `push_back()`](https://stackoverflow.com/questions/4303513/push-back-vs-emplace-back) because it constructs a new object from the given arguments, typically using [placement-new](https://isocpp.org/wiki/faq/dtors#placement-new), instead of calling a copy or move constructor like push_back)) – maddouri Oct 17 '15 at 19:07
  • At the point that your `A::add` is called `b` apparently has two owners. It is owned by the local `b` variable but also owned by `c`. I thought the idea was that each `b` object would have one unique owner? I don't think you can work around the problem just by using `release`. How do you expect your `add` to be used? – Chris Drew Oct 17 '15 at 21:29
  • I guess the OP would have to provide more context for me to answer that particular question :) In the provided solution, I was just trying to adhere to what seemed (for me) to be the original code's logic. – maddouri Oct 17 '15 at 21:38
  • @ChrisDrew yes every B object should have just one owner always. However the code is just an example. In the real code A and C doesn't know each other and the responsiblity for calling remove b from C and adding b to A belongs to another class. However I got the idea from code above. – Govan Oct 18 '15 at 08:58
1

I would only use unique_ptr if you have to. You may prefer to make B a move-only type (like unique_ptr) to restrict ownership.

If B is expensive to move or it is not practical to prevent the copying of B then use unique_ptr but be aware that you are then paying for a dynamic memory allocation.

Here is how you could use a move-only B in an example inspired by your code. If you use unique_ptr instead it should work exactly the same:

struct B {
    B();
    B(B&&) = default;                 // Explicitly default the 
    B& operator=(B&&) = default;      // move functions.

    B(const B&) = delete;             // Delete copy functions - Not strictly 
    B& operator=(const B&) = delete;  // necessary but just to be explicit.
};

struct A {
    std::vector<B> bs;
    void add(B b){
        bs.push_back(std::move(b));
    }
    B remove(std::vector<B>::iterator itr){
        B tmp = std::move(*itr);
        bs.erase(itr);
        return tmp;
    }  
};

struct C {
    A& a;
    std::vector<B> bs;
    C(A& a) : a(a) {}

    ~C(){
        for (auto& b : bs) {
            a.add(std::move(b));
        }
    }  // bs will be deleted now anyway, no need to remove the dead objects

    void add(B b){
        bs.push_back(std::move(b));
    }
    B remove(std::vector<B>::iterator itr){
        auto tmp = std::move(*itr);
        bs.erase(itr);
        return tmp;
    }
};

int main() {
    A a;
    C c(a);
    a.add(B());
    auto tmp = a.remove(a.bs.begin());
    c.add(std::move(tmp));
}

Live demo.

Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • thank you @ChrisDrew. The main reason I am using unique_ptr is learning. Otherwise it is really much easier with passing value. I wonder why c++ has pass by reference when it is so complicated... – Govan Oct 18 '15 at 19:20