【问题标题】:Use move semantics and vector of references to children of abstract classes对抽象类的子级使用移动语义和引用向量
【发布时间】:2017-01-19 07:19:26
【问题描述】:

我正在尝试(未成功)创建对象向量并使用 polimophism,而无需手动内存管理和任何类型的指针(*、unique_ptr、shared_ptr、weak_ptr 等)依赖于 raii 习语。

#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;
}

但它不能编译。我做错了什么?

【问题讨论】:

  • "vector&lt;A&gt; &amp;&amp;bar;" 应该是什么?
  • 右值引用

标签: c++ class oop move-semantics raii


【解决方案1】:
std::vector<A> something

是一个向量,它为A 动态分配存储空间,并且仅在添加对象时为A 分配存储空间。

vector的存储类型是A,它存储了A的实例。

当您将第一个 C 推到它上面时,它会执行以下操作:

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 {
      ..
    }
}

因为C 继承自A,所以它可以向下转型。但是,它只会复制传递给它的对象的A 部分,因为向量的存储类型是A,所以C 中的int bar 不是复制。

这称为切片,解决它的唯一方法是动态(指针或 std::any),或使用联合的危险路径。

-- 编辑--

次要问题是您希望std::vector&lt;A&gt; &amp;&amp;bar 做什么?它引用的临时对象在构造函数表达式的末尾过期:

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

-- 编辑2--

您的问题标题包含“使用移动语义”,但如编辑 1 中所述,您只存储一个右值 reference。移动语义与转移有关:考虑这个简单的 strdup 包装器:

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; }
    // ...
};

您的 B 类需要将向量和std::move 输入参数值实际存储到其构造函数中,而不是右值引用,以调用它的移动构造函数:

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
};

【讨论】:

  • 您的Strdupper::operator=(const Strdupper&amp;) 内存泄漏。
  • @aschepler 哎呀,已修复。
  • 感谢您的回答。如果我们得到右值引用,为什么要在构造函数中使用 std::move?
  • @KOLANICH 因为std::vector&lt;X&gt;&amp;&amp; vec 接收一个右值参数,但是因为我们给它一个名字而变成了一个左值。如果我们删除std::move(vec),我们将传递一个lvaluevectors 构造函数并复制而不是移动。 std::move 不做任何事情,它只是返回一个右值引用到你传递给它的东西。
猜你喜欢
  • 2015-07-02
  • 2020-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-10
  • 2013-07-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多