-1

I'm trying (unsuccesfully) to create a vector of objects and use polimophism without manual memory management and any kind of pointers (*, unique_ptr, shared_ptr, weak_ptr, etc) relying on raii idiom.

#include <iostream>
#include <vector>
using namespace std;

struct A{
    virtual void foo()=0;
    virtual ~A(){}
};
struct C:A{
    int bar;
    C(int bar):bar(bar){}
    virtual void foo(){
        cout << bar << endl;
    }
    virtual ~C(){}
};
struct B:A{
    vector<A> &&bar;
    int f;
    B(int f, vector<A> bar):f(f),bar(bar){}
    B(int f, vector<A> &bar):f(f),bar(bar){}
    B(int f, vector<A> &&bar):f(f), bar(bar){}
    virtual void foo(){
        cout << f << endl;
        for(auto &f: bar){
            f.foo();
        }
    }
    virtual ~B(){}
};

int main() {
    B b{1, vector<A>{C{1},C{2}}};
    b.foo();
    return 0;
}

But it doesn't compiles. What do I do wrong?

KOLANICH
  • 2,904
  • 2
  • 20
  • 20

1 Answers1

2
std::vector<A> something

is a vector which dynamically allocates storage for A, and only A, when objects are added.

The storage type of the vector is A, it stores instances of A.

When you push the first C onto it, it does something along the lines of:

void push_back(const A& a)  // NOTE: A, not C, downcasting occurs
{
    // extreme aproximation
    if (something.begin_ == nullptr) {
      something.begin_ = new A;
      something.begin_[0] = a;  // A::operator=(const A&);
      something.capacity_ = something.end_ = something.begin_ + 1;
    } else {
      ..
    }
}

Because C inherits from A it is able to downcast to this. However, it is only going to copy the A portion of the object passed to it, because the storage type of the vector is A and so your int bar in C is not copied.

This is called slicing, and the only way around it is either dynamic (pointers or std::any), or the perilous path of using a union.

-- Edit --

A secondary issue is what you expect std::vector<A> &&bar to do? The temporary objects to which it references expires at the end of the constructor expression:

B b{1, vector<A>{C{1},C{2}}};  // temporary vector is destroyed
// b.bar is dangling rvalue reference

-- Edit 2 --

Your question title contains "use move semantics" but as mentioned in edit 1, you only store an rvalue reference. Move semantics are about transferance: consider this simple strdup wrapper:

struct Strdupper {
    char* str_;
    Strdupper() = delete;
    Strdupper(const char* str) : str_(strdup(str)) {}
    ~Strdupper() { if (str_) free(str_); }
    Strdupper(const Strdupper& rhs) { str_ = strdup(rhs.str_); }
    Strdupper(Strdupper&& rhs) str_(rhs.str_) { rhs.str_ = nullptr; }
    Strdupper& operator=(const Strdupper& rhs) { if (str_) free(str_); str_ = strdup(rhs.str_); return *this; }
    Strdupper& operator=(Strdupper&& rhs) { std::swap(str_, rhs.str_); return *this; }
    // ...
};

Instead of an rvalue reference, your class B needs to actually store the vector and std::move the input parameter value to its constructor to call it's move-constructor:

class B {
    std::vector<X> vec_;

public:
    B() = default;
    B(const std::vector<X>& vec) : vec_(vec) {}  // copy semantics
    B(std::vector<X>&& vec) : vec_(std::move(vec)) {}  // move semantics
};
kfsone
  • 23,617
  • 2
  • 42
  • 74
  • Your `Strdupper::operator=(const Strdupper&)` leaks memory. – aschepler Sep 11 '16 at 21:46
  • @aschepler Whoops, fixed. – kfsone Sep 11 '16 at 21:47
  • Thank you for the answer. Why should we use std::move in constructor if we get rvalue reference? – KOLANICH Sep 12 '16 at 06:44
  • @KOLANICH Because `std::vector&& vec` *receives* an rvalue parameter but becomes an lvalue because we give it a name. If we removed the `std::move(vec)` we would be passing an *lvalue* to `vector`s constructor and copying rather than moving. `std::move` doesn't do anything, it just returns an rvalue-reference to the thing you pass it. – kfsone Sep 12 '16 at 07:01