【问题标题】:How do you return an object that takes pure virtual (const ref) arguments in C++?如何在 C++ 中返回一个采用纯虚拟(const ref)参数的对象?
【发布时间】:2017-06-30 01:06:14
【问题描述】:

我有一个包含抽象类实例列表的对象。

class A { };  // abstract class

class B {
 public:
  void addA(const A& a) {
    list_of_a_.push_back(&a);
  }
 private:
  std::vector<const A*> list_of_a_;
};

A 是抽象的,所以我将它们作为指针存储在向量中,并将其作为 const 引用传递。我有不同的 A 子类。

然后我有一个类似工厂的函数来创建 B 并返回它。

B CreateB() {
  B b;
  DerivationOfA a;  // subclass of abstract class A.
  b.addA(a);
  return b;
}

int main() {
  B b = CreateB();
  b.DoSomethingWithA();  // bad memory, 'a' was destroyed.
}

问题是一旦 B 从“create”函数返回,A 的实例就会在堆栈上被销毁。当我使用智能指针时也会发生同样的事情。我想避免使用常规指针来避免额外的内存管理问题。

这样做有什么诀窍,这样我就可以保留CreateB 函数,避免使用指针,但仍然能够将抽象类传递给B?我可以使用按值传递,只是 C++ 不允许对抽象类这样做。

编辑:我知道它为什么会发生,但我不确定在使用纯虚拟对象时如何绕过它。除了使用我想避免的 C 指针之外,我不确定如何避免范围问题。

编辑 2:更精确地说,纯虚拟 类 而不是 对象。

编辑 3:抽象 类而不是 纯虚拟 类以满足吹毛求疵。

【问题讨论】:

  • 这与虚函数或抽象类无关,而是与您存储指向局部变量的指针有关。如果您有抽象基类,请考虑一直使用指针,然后考虑指针所有权以及如何使用智能指针管理它们。
  • 没有“纯粹的虚拟对象”这样的想法。术语“纯虚拟类”可以翻译为“无法实例化的类”,即不能创建该类的对象
  • 您需要 new 向上 DerivationOfA (或者如果您愿意,可能是 make_unique 或 make_shared,这在内部最终将执行 new 等)。没有办法解决它,因为您不能保留指向局部变量的指针,并且在支持派生类的同时不能做任何类似 vector 的事情。
  • 尝试移动语义?
  • 纯虚函数。抽象类。

标签: c++ pointers inheritance memory pure-virtual


【解决方案1】:

CreateB 函数在堆栈上创建一个对象 b,当我们从该函数返回时,堆栈被展开,b 超出范围。

您可以在main 函数中创建对象b,然后对该对象调用addA 函数。

编辑

在堆上创建b 并返回对它的引用。

B* CreateB() {
    B *b = new B;
    // rest of the code
    return b;
}

【讨论】:

  • 这就是我最初拥有的,但它变得混乱(上面是整个代码的一个非常微不足道的例子)。我想要一个工厂函数来处理混乱并保持 main() 干净。
  • 您可以使用new 在堆上分配b。 B *b = 新 B;
  • 不应该return *b 是return b? b 是一个指针,所以返回它就像返回某物的引用
【解决方案2】:

您的设计令人困惑。通过引用接收对象并保存其指针是个坏主意。

  • 使用智能指针。
  • 如果您想通过指针保留对象,您需要在堆上分配它(通过new 运算符)并在您的容器(具有向量成员的类)中释放(通过delete 运算符)。
  • 如果你想通过引用传递它,你需要保证该对象不会被删除。或者您需要在该函数调用期间复制该对象。

在某些库中,容器接收带有刚刚创建的对象的指针并将其保存在内部数据中并负责将其删除。如果您不使用智能指针,则需要定义并记住哪个对象是指针引用的对象的主人。通过指针来管理对象并不是一个简单的问题。聪明的指针让这更简单。

【讨论】:

  • 我最终使用智能 shared_ptr 来存储这些对象。这样它可以被复制并避免堆栈。
【解决方案3】:

为 B 定义一个移动构造函数。

B factory() {
  B x;
  // some code

  return std::move(x);
  // return x; if with copy elision
}

应该可以。

如果移动语义不是一个选项。

struct B {
 public:
  class Holder {
   public:
    static Holder NewHolder() {
      return Holder(new B{});
    }
    B *ptr_;

   private:
    Holder(B *ptr) : ptr_(ptr) {}
  };

  B() : resource_(new int) {
    std::cout << "Construct" << std::endl;
  }

  B(Holder x) {
    resource_ = x.ptr_->resource_;
    x.ptr_->resource_ = nullptr;
    delete x.ptr_;
  }

  ~B() {
    std::cout << resource_ << " deleted by " << this << std::endl;
    delete resource_;
  }

  int *resource_;
};

B::Holder factory() {
  B::Holder holder = B::Holder::NewHolder();
  B &b = *holder.ptr_;
  // some code

  return holder;
}

int main() {
  B b = factory();

  return 0;
}

Holder 是一个嵌套类,其中包含一个由 new 分配的 B。并且这个B不会被销毁,直到构造函数B(Holder x)从持有者那里窃取资源,然后构造函数删除空的B。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-19
    • 1970-01-01
    • 1970-01-01
    • 2011-03-21
    • 1970-01-01
    • 2018-06-01
    • 1970-01-01
    相关资源
    最近更新 更多