【问题标题】:Define an empty default constructor with no default constructible data member定义一个没有默认可构造数据成员的空默认构造函数
【发布时间】:2015-10-24 15:08:23
【问题描述】:

考虑以下示例:

class MyWrapper {
    public:
        MyWrapper() {};
    private:
       ThirdPartyLibraryType impl;
};

假设ThirdPartyLibraryType 没有默认构造函数,我也无能为力,因为它是第三方库类型。那么这段代码将无法编译,因为默认构造函数MyWrapper() 必须调用ThirdPartyLibraryType 的默认构造函数。现在我只想让默认构造函数什么都不做,因为我使用默认构造函数的用例如下:

std::array<MyWrapper,10> myArray;
for (int i=0;i<10;++i) {
    myArray[i] = generateMyWrapper(...);
}

有没有办法强制生成默认构造函数? (没有开销,我不想使用ThirdPartyLibraryType* 具有数据成员的明显解决方案)

编辑: 到现在为止,我使用的方法类似于 Yakk 提出的方法,不依赖默认构造函数,但我认为成员函数调用的情况很丑:

template<class T, size_t N, class C, class CF>
std::array<T,N> gen_array(C const& obj, CF&& f) {
    ...
    use obj.f
    ...
}

class MyClass {

    MyWrapper generateMyWrapper(int i) const { ... }

    auto genMyWrapperTypeArray() const {
        return 
            gen_array<
                 MyWrapper,10,MyClass,
                 MyWrapper(MyClass::*)(int) const
            > (
                *this, &MyClass::generateMyWrapper
            );
    }
}

我还没有测试过,我的意思是,如果语法不完全相同,它仍然会比我在默认构造函数可用时使用的简单 for 循环复杂得多。

【问题讨论】:

  • 也许我误解了你的问题,你能用一些默认值调用ThirdPartyLibraryType的ctor吗?如MyWrapper() : impl(some_default_value) {};
  • 如果重点是在没有默认构造函数的情况下初始化数组,为什么不直接切换到向量呢? vector&lt;MyWrapper&gt; myVector; for (int i=0;i&lt;10;++i) myVector.push_back(generateMyWrapper(...)); ?
  • @songyuanyao 是的,你可以这样做。有两个问题:1:我不知道默认构造函数的有效 some_default_value 2:代码是模板化的,ThirdPartyLibraryType 引用了许多不同的类型,可能具有不同的非默认构造函数
  • @Christophe 是的,我可以做到这一点,但矢量速度较慢。但是,也许可以实现类似于 std::array 的不强制元素默认构造的类型...

标签: c++ c++11 default-constructor regular-type


【解决方案1】:

std::experimental::optional 或 boost 等效项具有适度的开销,但处理“这可能是或未构造”。

不安全的等价物是std::aligned_storage_t,您可以在其中进行构建。问题在于破坏,你需要知道它是否被构造。

更好的选择可能是整个数组都是可选的,并且根本没有零参数 ctor。打包构建每个参数所需的内容并创建一个构造函数,而不是循环。

template<class T, size_t...Is, class F>
std::array<T,sizeof...(Is)> gen_array(
  std::index_sequence<Is...>, F&&f
){
  return {{f(Is)...}};
)


template<class T, size_t N, class F>
std::array<T,N> gen_array(
  F&&f
){
  return gen_array(std::make_index_sequence<N>{}, f);
)

上面调用传递的 lambda 与每个元素的索引来构造元素。

【讨论】:

  • 我想我使用了你提到的第三种解决方案:我有一个 MyArray 类,其中一个 ctor 将生成器作为参数。但在某些情况下它往往会变得丑陋,主要是当函数实际上是一个成员函数时(丑陋的调用语法,this...的处理)
  • @b lambda 让它变得简单吗?使用[&amp;](size_t i){ return blah;}?
  • 我编辑了我的问题以显示成员函数案例。也许我可以用 lambda 改进语法,但我不知道,我一点也不流利。
  • @b 调用我的数组 gen,传递 lambda,用最终返回数组元素的任意代码替换 return blah。
  • 好的,我知道了。确实,lambda 让它更干净
【解决方案2】:

如果ThirdPartyLibraryType 没有默认构造函数,这可能是由于有效的设计决策 / 原因。

然后你有两种方法:

  • 要么为非默认构造函数找到一些合适的参数,然后在包装器中使用它们;

  • 或者你延迟 ThirdPartyLibraryType 的构造,直到你知道要使用哪个构造函数以及使用哪些参数。

第一种方法如下所示:

class MyWrapper {
    public:
        MyWrapper() : impl(/*parameters that you've selected*/) {};
    private:
       ThirdPartyLibraryType impl;
};

也许它适合您的需要,因为您已经知道要使用哪些构造函数参数,或者因为您可以负担得起构造一个便宜的对象并在以后覆盖它。但这并不总是一个好主意...

第二种方法将使用您想要避免的指针。这种方法有很多优点:

  • 您可以像示例中那样构造巨大的默认初始化数组,并用生成的对象覆盖不昂贵的默认包装器。
  • 您尊重您所依赖的库的设计,只构建有意义的对象。
  • 您可以减少对第三方库的依赖:需要您的包装器的编译单元不需要知道第三方类型,也不需要包含第三方库的头文件(前向声明就足够了)。

事实上,显而易见的指针方法将使您的包装器实现PIMPL idiom

【讨论】:

  • 默认构造的对象无效。我的观点是它会在循环之后被初始化。在构造时使其有效的替代方案很难实现并且在语法上很难看(我会在一分钟内发布一个示例)。
  • 我同意在没有默认值的情况下强制默认值是丑陋的。我试图以外交方式表达它(事实上在一些罕见的情况下它可以提供帮助)。但是,要么一个成员属于该类,并且需要使用该类的对象构造它,要么您使用一个指针。同时,我已经详细说明了第二种方法,因为我认为在查看生成循环时它肯定会更有意义。
  • 感谢您的回答。我知道 pimpl 成语,但这不是我要在这里实现的目标。我也知道有完全有效的参数不能使默认构造函数可用。我曾经认为这些论点比控制流中的便利性和灵活性的论点更重要,但我现在不这么认为了。另外,如果您查看概念提案中的人员,他们似乎会告诉您,您的对象应该是他们所谓的 Regularsemiregular,其中包括 default-constructible
猜你喜欢
  • 2019-11-22
  • 2012-09-01
  • 2023-03-20
  • 1970-01-01
  • 1970-01-01
  • 2016-07-18
  • 1970-01-01
  • 1970-01-01
  • 2014-12-21
相关资源
最近更新 更多