逐渐构建一个对象

时间:2014-03-05 07:45:18

标签: c++

假设存在两个类(class Derived: public Base)的层次结构。这两个类都有很大的内存占用和昂贵的构造函数。请注意,这些类中没有任何内容在堆中分配:它们只有一个大的sizeof

然后有一个具有快速路径(始终执行)和慢速路径(有条件地执行)的功能。快速路径需要Base实例,慢速路径需要从现有基础构建的Derived实例。此外,只有在快速路径之后才能做出慢速路径决定。

当前代码如下所示:

void f()
{
    Base base;
    /* fast path */

    if (need_slow_path) {
        Derived derived (base);
        /* slow path */
    }
}

这是低效的,因为需要将基础复制到派生中;基数也被分配两次,存在堆栈溢出的风险。我想要的是:

  • Derived实例
  • 分配内存
  • 致电Base ctor
  • 执行快速路径
  • 如果需要,请在现有Derived实例上调用Base ctor并执行慢速路径

在C ++中有可能吗?如果没有,有哪些可行的解决方法?显然,我正在努力优化速度。

7 个答案:

答案 0 :(得分:32)

我担心这不可能就像你写的那样 - Derived 的任何构造函数必须调用Base子对象的构造函数,所以唯一的方法就是这样做合法的是首先打电话给Base的析构函数,我相信你不希望这样。

但是,通过轻微的重新设计应该很容易解决这个问题 - 更喜欢组合而不是继承,并使Derived成为一个单独的类来存储引用(在一般意义上;它当然可以是一个指针)到Base并使用它。如果访问控制是个问题,我觉得friend在这里是合理的。

答案 1 :(得分:11)

您应该稍微更改您的设计,以便将您对继承的依赖性更改为合成。

您可以将派生类的成员(不存在于基类中)封装到另一个类中,并在派生类中保留它的空引用。

现在直接初始化派生类而不初始化新类的对象。

每当需要慢速路径时,您可以初始化并使用它。

<强>优势

  • 保留派生类和基类之间的继承关系。
  • 永远不会复制基类对象。
  • 您对派生类进行了延迟初始化。

答案 2 :(得分:7)

我可以伪造它。

将派生的所有数据移动到optional(无论是booststd::ts::optional提案,用于发布C ++ 14,还是手动滚动)。

如果您想要慢速路径,请初始化optional。否则,请将其保留为nullopt

将有bool开销,并在您分配/比较/销毁隐式时进行检查。 virtual函数之类的内容将是derived(即,您必须手动管理动态调度)。

struct Base {
  char random_data[1000];
  // virtual ~Base() {} // maybe, if you intend to pass it around
};
struct Derived:Base {
  struct Derived_Data {
    std::string non_trivial[1000];
  };
  boost::optional< Derived_Data > m_;
};

现在我们可以创建一个Derived,并且只有在m_.emplace() Derived_Data之后才能构建bool。仍然存在的所有东西都在一个连续的内存块中(optional注入m_以跟踪是否构造了{{1}}。

答案 3 :(得分:4)

不确定你是否可以做到你想要的,即在第二个构造之前执行“快速”路径,但我认为你使用'placement new'功能 - 手动调用基于need_slow_path谓词的构造函数。但是变化的流量有点小:

  • 为派生实例分配内存
  • 在其上调用Base或Derived ctor
  • 执行快速路径
  • 执行慢速路径(如果需要(

示例代码

#include <memory> 
void f(bool need_slow_path)
{
    char bufx[sizeof(Derived)];
    char* buf = bufx;

    Derived* derived = 0;
    Base* base = 0;

    if (need_slow_path ) {
        derived = new(buf) Derived();
        base = derived;
    } else {
        base = new(buf) Base();
    }

    /* fast path using *base */

    if (need_slow_path) {
        /* slow path using *base & * derived */
    }

    // manually destroy
    if (need_slow_path ) {
        derived->~Derived();
    } else {
        base->~Base();
    }
}

此处详细介绍了新的展示位置:What uses are there for "placement new"?

答案 4 :(得分:2)

您可以在编译器中定义move copy con't吗?

这里有一个很好的解释(虽然有点长)

https://skillsmatter.com/skillscasts/2188-move-semanticsperfect-forwarding-and-rvalue-references

我没有移动语义的经验所以我可能错了,但是因为你想避免在将基础对象传递给派生类时应对基础对象移动语义应该做的伎俩

答案 5 :(得分:2)

首先将构造函数代码提取到BaseDerived的初始化方法中。

然后我会使代码与此类似:

void f()
{
    Derived derived;
    derived.baseInit();
    /* fast path */

    if (need_slow_path) {
        derived.derivedInit();
        /* slow path */
    }
}

如Tanmay Patil在答案中所建议的那样提取课程和使用作文是一个好主意。

还有另一个提示:如果你还没有完成,请进入单元测试。他们帮助您处理大型课程。

答案 6 :(得分:2)

也许不是类和构造函数,而是需要一个普通的结构和初始化函数。当然,您将放弃许多C ++的便利,但您将能够实现优化。