1

I've a class Foo<T> which has a vector of smart pointers to Shape derived classes. I'm trying to implement an at(index) member function. Here's what I would to do intuitively:

Foo<float> myfoo;
std::unique_ptr<Shape<float>> shape_ptr = myfoo.at(i);
shape_ptr->doSomething(param1, param2, ...);

When defining the at(index) function, I'm getting a compiler error message. Note that the move constructor was defined and that the Shape base class is abstract. Below, I'm giving some code for illustration purposes.

Furthermore, I found recently on the web an example on how to overload the assignment operator using std::move. I usually follow the Copy-Swap idiom. Which of those two ways for overloading the mentioned operator makes sense for my case? Below, I'm also illustrating the function's definition.

template < typename T >
class Foo{

    public:

        Foo();
        Foo( Foo && );
        ~Foo();

        void swap(Foo<T> &);
        //Foo<T> & operator =( Foo<T> );
        Foo<T> & operator =( Foo<T> && );

        std::unique_ptr<Shape<T> > at ( int ) const; // error here!

        int size() const;

    private:

        std::vector< std::unique_ptr<Shape<T> > > m_Bank;
};

template < typename T >
Foo<T>::Foo( Foo && other)
    :m_Bank(std::move(other.m_Bank))
{

}

/*template < typename T >
void Filterbank<T>::swap(Filterbank<T> & refBank ){

    using std::swap;
    swap(m_Bank, refBank.m_Bank);
}

template < typename T >
Foo<T> & Filterbank<T>::operator =( Foo<T> bank ){

    bank.swap(*this);
    return (*this);
}*/

template < typename T >
Foo<T> & Foo<T>::operator =( Foo<T> && bank ){

    //bank.swap(*this);
    m_Bank = std::move(bank.m_Bank);
    return (*this);
}

template < typename T >
std::unique_ptr<Shape<T> > Foo<T>::at( int index ) const{
    return m_Bank[index]; // Error here! => error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty>'
}
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
Tin
  • 1,006
  • 1
  • 15
  • 27
  • 1
    Unique pointers have **unique ownership semantics**, so it makes no sense to make a copy of one. What do you want to happen? Do you want a deep copy of the object? – Kerrek SB Dec 12 '11 at 19:52
  • @KerrekSB, no, i don't want a deep copy of the object. The idea is that i first fill the `Shape` m_Bank vector. Once it's filled, I just want to call the corresponding `doSomething` function (e.g triangle->doSomething(), square->doSomething), sth. like: `myfoo.m_Bank.at(i)->doSomething(param1, param2, ...);` but the m_Bank is a private class member, therefore, I need a member function that allows me to access the element at index `i` – Tin Dec 12 '11 at 19:57
  • Such an interface would be extremely surprising (i.e. "bad"), because you could only ever call `at(i)` *once*, with `std::move`, and it would destroy the vector's element value. Do you want a shared pointer instead? If you really just want the vector as a staging area, then I think you need to reconsider the design. – Kerrek SB Dec 12 '11 at 19:59
  • @KerrekSB, I don't need to return actually the unique_ptr, I just wanted to have an interface that allows me to do: `myfoo.m_Bank.at(i)->doSomething(param1, param2, ...);` from the main.cpp. Given that m_Bank is private I can't access the vector, therefore I was lookinf for an class interface. – Tin Dec 12 '11 at 20:07
  • Well, you could expose the raw pointer (`m_Bank[i].get()`), but you might not like the idea that someone could try to `delete` that pointer (though that wouldn't be your fault). Alternatively, you could implement `myfoo.doSomething(i, param1, param2, ...)`... – Kerrek SB Dec 12 '11 at 20:28

4 Answers4

2

Use Boost's pointer containers instead of a standard container with unique_ptr. They're designed for this kind of usage.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
1

I think you should be using shared_ptr here instead.

Only one unique_ptr can own the shared resource. If you are able to do what you intend ie return a unique_ptr by value then the one in the vector will be destroyed which is probably what you don't want.

parapura rajkumar
  • 24,045
  • 1
  • 55
  • 85
  • @ parapura, I basically need to call the corresponding `doSomething` function of the element at `index i` of my vector. It doesn't need to return a `unique_ptr`. I just need to do sth. like: `bar = myfoo.m_Bank.at(i)->doSomething(param1, param2, ...);` in the main.cpp, but the m_Bank is a private class member. – Tin Dec 12 '11 at 20:06
  • @ parapura, when using shared_ptr, then there's no need for defining either move constructor or move assignment operator, right? – Tin Dec 14 '11 at 14:35
  • @Tin: The copy constructor and copy assignment for `vector>` are O(N). The copy constructor will require one memory allocation. The copy assignment operator may require a deallocation, and an allocation. The move constructor of a `vector>` is O(1) and will not require any memory allocation or deallocation. The move assignment is in general O(N) and may require a deallocation, but will not require an allocation. In the typical use case for the move assignment operator, it is O(1) and the deallocation will not be required. – Howard Hinnant Dec 14 '11 at 15:32
1

Q1: What to do with Foo::at( int ) const such that you can:

myfoo.at(i)->doSomething(param1, param2, ...);

without transferring ownership out of the vector<unique_ptr<Shape<T>>>.

A1: Foo::at( int ) const should return a const std::unique_ptr<Shape<T> >&:

template < typename T >
const std::unique_ptr<Shape<T> >&
Foo<T>::at( int index ) const
{
    return m_Bank[index];
}

Now your can dereference the const unique_ptr and call any member of Shape they want (const or non-const). If they accidentally try to copy the unique_ptr, (which would transfer ownership out of Foo) they will get a compile time error.

This solution is better than returning a non-const reference to unique_ptr as it catches accidental ownership transfers out of Foo. However if you want to allow ownership transfers out of Foo via at, then a non-const reference would be more appropriate.

Q2: Furthermore, I found recently on the web an example on how to overload the assignment operator using std::move. I usually follow the Copy-Swap idiom. Which of those two ways for overloading the mentioned operator makes sense for my case?

A2: I'm not sure what ~Foo() does. If it doesn't do anything, you could remove it, and then (assuming fully conforming C++11) you would automatically get correct and optimal move constructor and move assignment operator (and the proper deleted copy semantics).

If you can't remove ~Foo() (because it does something important), or if your compiler does not yet implement automatic move generation, you can supply them explicitly, as you have done in your question.

Your move constructor is spot on: Move construct the member.

Your move assignment should be similar (and is what would be automatically generated if ~Foo() is implicit): Move assign the member:

template < typename T >
Foo<T> & Foo<T>::operator =( Foo<T> && bank )
{
    m_Bank = std::move(bank.m_Bank);
    return (*this);
}

Your Foo design lends itself to being Swappable too, and that is always good to supply:

friend void swap(Foo& x, Foo& y) {x.m_Bank.swap(y.m_Bank);}

Without this explicit swap, your Foo is still Swappable using Foo's move constructor and move assignment. However this explicit swap is roughly twice as fast as the implicit one.

The above advice is all aimed at getting the very highest performance out of Foo. You can use the Copy-Swap idiom in your move assignment if you want. It will be correct and slightly slower. Though if you do be careful that you don't get infinite recursion with swap calling move assignment and move assignment calling swap! :-) Indeed, that gotcha is just another reason to cleanly (and optimally) separate swap and move assignment.

Update

Assuming Shape looks like this, here is one way to code the move constructor, move assignment, copy constructor and copy assignment operators for Foo, assuming Foo has a single data member:

std::vector< std::unique_ptr< Shape > > m_Bank;

...

Foo::Foo(Foo&& other)
    : m_Bank(std::move(other.m_Bank))
{
}

Foo::Foo(const Foo& other)
{
    for (const auto& p: other.m_Bank)
        m_Bank.push_back(std::unique_ptr< Shape >(p ? p->clone() : nullptr));
}

Foo&
Foo::operator=(Foo&& other)
{
    m_Bank = std::move(other.m_Bank);
    return (*this);
}

Foo&
Foo::operator=(const Foo& other)
{
    if (this != &other)
    {
        m_Bank.clear();
        for (const auto& p: other.m_Bank)
            m_Bank.push_back(std::unique_ptr< Shape >(p ? p->clone() : nullptr));
    }
    return (*this);
}

If your compiler supports defaulted move members, the same thing could be achieved with:

    Foo(Foo&&) = default;
    Foo& operator=(Foo&&) = default;

for the move constructor and move assignment operator.

The above ensures that at all times each Shape is owned by only one smart pointer/vector/Foo. If you would rather that multiple Foos share ownership of Shapes, then you can have as your data member:

std::vector< std::shared_ptr< Shape > > m_Bank;

And you can default all of move constructor, move assignment, copy constructor and copy assignment.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • @ Howard, nice explanation! In regard to your answer `A1`: `if you want to allow ownership transfers out of Foo via at, then a non-const reference would be more appropriate.`. It's actually not my case, but to clear my doubt, is it actually really a transfer when I set the return type as a `non-const reference`? This would mean that the element in turn at the vector will be then deleted,is it right? From what I understand, the last `const` is to ensure that the state of the object is not going to be changed and the first `const` is to ensure the retrieved element does not change, right? – Tin Dec 13 '11 at 15:47
  • @ Howard, one more thing. How could I use the `at()` member function you proposed? I was thinking sth. like: `std::unique_ptr > ptrShape (foo_obj.at(i))` or `std::unique_ptr > ptrShape = foo_obj.at(i)`. However, I get the following compiler error message: `error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty>'` – Tin Dec 13 '11 at 16:07
  • @Tin: If you returned a `unique_ptr` by non-const reference, that would allow clients to move from it, thus leaving a null-valued `unique_ptr` in the `vector`. But one can not move from a `const unique_ptr&` (you get a compile time error if you try). If `at` returned a `unique_ptr` by value (as in your question), you can make that work too with `return move(m_Bank[index]);`, however that means that every time the client calls `at(i)`, the ownership is transferred to the client, with no explicit act required by the client. – Howard Hinnant Dec 13 '11 at 17:25
  • @Tin: The first `const` is to ensure that ownership is not transferred out of `Foo`. The second `const` ensures that nothing about `Foo` changes (such as the size of the `vector`). There is nothing here that prevents a change to the `Shape`. I.e. you can call a non-const member function on `Shape` with this design. – Howard Hinnant Dec 13 '11 at 17:26
  • @Tin: The way to use this is very similar to how you described in a comment under your question: `myfoo.at(i)->doSomething(param1, param2, ...);`. If you try to make a copy of the `const unique_ptr&` returned by `at`, that results in a compile time error (as you noted in your comment). That copy would represent a ownership transfer out of the vector. – Howard Hinnant Dec 13 '11 at 17:29
  • @Tin: If you *also* want to prevent non-const things from happening to the `Shape`, replace `unique_ptr` with `unique_ptr` in all places used. – Howard Hinnant Dec 13 '11 at 17:36
  • @ Howard, thanks for the answers. One more thing, given the swap member function that you suggested, how does the definition of the assignment operator would look like? – Tin Dec 16 '11 at 13:55
  • @Tin: The move assignment operator should look just like I show it above the swap definition. The copy assignment operator is implicitly deleted. – Howard Hinnant Dec 16 '11 at 15:45
  • @ Howard, does this means that whenever there's a copy assignment operator and a move assignment operator, the copy assignment operator gets implicitly deleted? Could one chose between which of both should be deleted? Furthermore, when exactly does the `friend void swap` function you defined is going to be used/called? For classes, where no memory is allocated, then neither a move constructor nor a move assignment operator are needed, right? In this case, the best solution would be to use the copy-swap-idiom, right? – Tin Dec 16 '11 at 23:07
  • @ Howard, thanks for the link! It's good to know that some compilers support now implicit move generation, great! Furthermore, what's still not clear from your third example of your post (`If you're dealing with a compiler that does not yet support defaulted special members...`), where does the `void swap(my_type &other)` member function is then used? Reading also the comments there, Luc Danton suggested to use the default std::swap, instead of string.swap. In which cases is it better to use the default std::swap method? – Tin Dec 21 '11 at 08:25
  • @ Howard, and also thanks because one other doubt was cleared, I tought, the move semantics was only used for moving `allocated memory` resources (i.e allocations on the heap by pointers), and it seems to be that it also is used for objects in the stack (i.e as the std::string name in your example). – Tin Dec 21 '11 at 08:30
  • @ Howard, and finally on more question. Given that the default copy assignment operator gets deleted, when we define our own move assignment operator, we must then also supply a copy assignment operator. How would you define an efficient copy assignment operator? Do you mind in illustrating it with an example? Is this here, where your `friend swap ` function gets called? – Tin Dec 21 '11 at 11:12
  • @Tin: In the `Foo` example above, nothing calls `swap`, unless it is the client of `Foo` calling `swap` explicitly. For example if you had a `vector` and called `std::reverse` with that vector, then `std::reverse` would call this `swap`. – Howard Hinnant Dec 21 '11 at 14:32
  • @Tin: On how to call `swap`: It can be a matter of style. I like to call member swap when I know that I'm dealing with a type that has it. But you don't have to do it that way. When dealing with a generic type, you should call the non-member swap, unqualified, prefixed with a `using std::swap`. – Howard Hinnant Dec 21 '11 at 14:35
  • @Tin: I can't write a copy constructor for `Foo` without knowing what the copy constructor is supposed to do. `vector>` isn't copyable. For example, should the copy constructor call a `clone()` member function on each `Shape`? Often when you find out that you need a copy constructor, you may be wanting shared ownership semantics, and `vector>` may be more appropriate. And then the defaulted copy constructor does exactly what you want. – Howard Hinnant Dec 21 '11 at 14:38
  • @ Howard, thanks for the clarifications. An example on what the copy constructor should do, can be found in the following link: http://pastebin.com/jKp7t1aT More specifically for the `ShapeContainer` class. In my case, I used `vector>` but after your suggestion, it seems to be that I need to change to `vector>`. – Tin Dec 21 '11 at 16:12
  • @ Howard, If I use `unique_ptr`, how would you define the `copy assignment operator`? and if I use `shared_ptr`, how do both `copy constructor` and `copy assignment operator` should be defined? Furthermore, following the tutorials in move semantics, the definition of the `move constructor` and `move assignment operator`, remains the same, right? – Tin Dec 21 '11 at 16:13
  • @ Howard, thanks for the update! In regard to the overload of the `copy-assigment operator`, would it be possible to use the `copy-swap` idiom for this case? – Tin Jan 04 '12 at 10:14
  • @ Howard, thanks! In regard to the `move constructor`, there's something strange going on. I'm following the same code, I originally posted. When I debug the line (before actually `moving` the vector's content) => `:m_Bank(std::move(other.m_Bank))`, the size of the `other.m_Bank vector` is around 1600 million of elements, although it should have only `11`. However, after performing the `std::move` from `other.m_Bank` to `this->m_Bank`, the latter has then the correct size, i.e. `11`. Any ideas? – Tin Jan 04 '12 at 19:04
  • When I have trouble believing my debugger I resort to print statements. – Howard Hinnant Jan 04 '12 at 19:45
  • @ Howard, what if the variables you want to print, are in the member initialization list of a constructor? The strange values I was pointing to, correspond actually to what my debugger showed for the variables in the move constructor initialization list, or are those values in any case incorrect? Furthermore, if I would define the `m_Bank` as `vector`, is the following [code](http://pastebin.com/BzP5jtws) correct for defining the `move constructor`, `move assignment`, `copy constructor` and `copy assignment`? – Tin Jan 05 '12 at 08:34
  • @ Howard, Here's a print-screen from my debugger [flickr.1](http://www.flickr.com/photos/73676442@N08/6640058027/in/photostream) and [flickr.2](http://www.flickr.com/photos/73676442@N08/6640064035/in/photostream) – Tin Jan 05 '12 at 08:50
0

It seems like you should just be returning a reference here:

Shape<T> & Foo<T>::at( int index ) const{
    return *m_Bank[index];
}
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • @ Benjamin, the std::vector would be `shared_ptr` or `unique_ptr`? By following your solution, when the `at` function exit, what happens exactly? – Tin Dec 12 '11 at 20:13
  • @Tin: *"the std::vector would be shared_ptr or unique_ptr?"* -- That's up to you. I was under the impression that you didn't want to change it. If you do change it to shared_ptr, then you can just return a shared_ptr, or a weak_ptr. But if you keep it as unique_ptr, that's where my solution applies. – Benjamin Lindley Dec 12 '11 at 20:16
  • @Tin: *"when the at function exit, what happens exactly?"* -- You have a reference to the object that the unique_ptr points to. And you can call methods of Shape on it, i.e. `myfoo.at(i).doSomething()` – Benjamin Lindley Dec 12 '11 at 20:17
  • @ Benjamin, and since we have only the reference to the object (not actually `std::moving` the pointer), the element is no longer destroyed from the vector, right? – Tin Dec 12 '11 at 20:20
  • @ Benjamin, and even more, we can call the `at()` function as many times as we wish, and not only `once`, right? Is it good to return by reference? In regard to the pointer type, I think, it's still not so clear to me, when shared_ptr over unique_ptr are prefered? In my case, I fill the the vector once. Then, I'll need to call the associated `doSomething` functions of the elements of the `vector` many times. – Tin Dec 12 '11 at 20:27
  • @Tin: Right, you can call it as many times as you wish. If you want, you could also store the reference, and call functions on that. Just make sure the variable you store it in is actually declared as a reference, otherwise, you will get a copy. – Benjamin Lindley Dec 12 '11 at 20:32
  • @Tin: Use shared_ptr if you want the lifetime controlled through multiple objects. Not for when you just want access from multiple objects. – Benjamin Lindley Dec 12 '11 at 20:34
  • @ Benjamin, that being said, since in my case I need to access to virtual functions of the multiple objects in the vector (`triangle->doSomething()`, `square->doSomething()`,...), it makes more sense to use unique_ptr. Did I get your idea correctly? Do you've an example illustrating when shared_ptr is preferred? – Tin Dec 12 '11 at 20:42