【问题标题】:Selecting between two constructors在两个构造函数之间进行选择
【发布时间】:2013-01-18 18:05:05
【问题描述】:

问题:我有一个带有两个构造函数的不可复制对象。我需要使用其中一个构造函数创建一个对象,然后在一些通用代码中使用它:-

使用可复制对象,它看起来像这样,而且很简单:

Object a;

if (condition) 
   a = Object(p1);
else
   a = Object(p2,p3,p4);

a.doSomething();

但是,该对象是不可复制的,所以我不得不这样做:

boost::scoped_ptr<Object> a;

if (condition) 
   a = new Object(p1);
else
   a = new Object(p2,p3,p4);

a->doSomething();

这感觉太复杂了。有没有更好的解决方案?

【问题讨论】:

  • 你不是copying,你是assigning,所以严格来说,如果类是non-copyable,只要是可分配.
  • .. 虽然通常两者齐头并进。当然,你希望如此。
  • @juanchopanza:我很难考虑提供赋值运算符但不提供复制构造函数的理由。
  • 答案是否定的。没有更好的解决方案。现在看看出现的无数答案实际上并没有回答问题。
  • 可以选择 C++11 吗?如果是,物体是否可移动?

标签: c++ constructor


【解决方案1】:

这是一个非常糟糕的 hack,假设 Object 是默认可构造的:

Object a;
a.~Object();

if (condition) { ::new (&a) Object(p1); }
else           { ::new (&a) Object(p2, p3, p4); }

Don't use this.

另一种选择是使用联合,但您还需要在该设置中手动调用析构函数。


使用Boost.Optional(使用in-place factories)可以实现更清洁的解决方案。 (感谢@K-Ballo 挖掘细节!)

#include <boost/optional.hpp>
#include <boost/utility/in_place_factory.hpp>

struct Object
{
    explicit Object(int) {}
    explicit Object(int, float, std::string) {}

    Object(Object const &)             = delete;
    Object(Object &&)                  = delete;
    Object & operator=(Object const &) = delete;
    Object & operator=(Object &&)      = delete;
};

boost::optional<Object> a;

if (condition) { a = boost::in_place(0); }
else           { a = boost::in_place(0, 1.0f, "two" ); }

【讨论】:

  • 可选有一些旧形式的就位,带有就地标志或类似的东西
  • @K-ballo:你说得对,in-place factories。本来想查的,后来忘记了。如果我发现更多信息,我会更新这篇文章。
  • @KerrekSB:它们的重点是将它们与不可复制构造的类型一起使用......但我从未使用过它们,我会看看我是否可以弄清楚他们
  • @KerrekSB:从示例(嗯,测试)来看,用法似乎是boost::optional&lt;Object&gt; v; v = boost::in_place&lt;Object&gt;(p2, p3, p4);
  • 接受这个答案,因为它教会了我最多,即使我要让代码保持不变!
【解决方案2】:

在我看来完全合理。它清晰、简单且相对简洁。

【讨论】:

  • 如果它没有引起不必要的分配
  • 其实分配的是实际对象本身。 scoped_ptr 位于堆栈上。
  • @thang:scoped_ptr 也是一个对象,并且具有自动存储持续时间,它可能需要也可能不需要任何“堆栈”。不过,它肯定有分配
  • @thang:“堆栈”上的分配仍然是分配
  • @thang:啊,“说一些与你的意思不同的东西”的旧原则。为什么我要引用“堆栈”?因为这个问题被标记为c++,这是一种高级计算机编程语言,从中提取了一些实现细节。首先,看这里stackoverflow.com/questions/4687310/memory-allocation-in-c,然后不要再做一个聪明人了。
【解决方案3】:
auto const doSomethingTo = []( Object&& o ) { o.doSomething(); };
doSomethingTo( condition? Object( p1 ) : Object( p1, p2, p3 ) );

免责声明:编译器未触及代码。


编辑:上面的代码,当 Object( Object&amp;&amp; ) 构造函数是 private 时,无法使用 MSVC 11.0 编译(甚至是去年 11 月的 CTP),但使用 MinGW g++ 4.7 可以正常编译.1 和某些版本的clang

看来it should compile

所以,这可能是 Visual C++ 中的一个错误——但不幸的是,我没有找到一个简单的解决方法。


对于假定为 Visual C++ 编译器错误的不容易解决方法:

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

class Object
{
private:
    Object( Object const& );
    Object( Object&& );
public:
    void doSomething() const {}
    Object( int ) {}
    Object( int, int, int ) {}
};

int main( int argc, char* argv[] )
{
    int p1 = 0, p2 = 0, p3 = 0;
    bool condition = argc == 2;

    auto const doSomething1 = [=]() { Object o( p1 ); o.doSomething(); };
    auto const doSomething2 = [=]() { Object o( p1, p2, p3 ); o.doSomething(); };

    if( condition ) { doSomething1(); } else { doSomething2(); }
}

另一个答案认为new(阅读:动态分配)是您唯一的选择。

错了。

【讨论】:

  • 实际生产中使用的大部分编译器都不接受你的方案。右值引用和 lambda 函数需要 C++11(尚未普及)。但基本思想可能是有效的:将所有使用对象的代码移动到一个通过引用获取对象的函数中,然后在调用它的函数中有两个不同的变量。
  • @JamesKanze:谢谢,我也很怀疑……是的,基本思想也适用于 C++03,它对应于最后显示的解决方法,或者应用一个函数产生对临时的引用,以便使用更直接的原始构造。我不明白为什么您必须在不合理的要求(默认可构造)+可能的副作用或动态分配效率低下+对大库的依赖之间进行选择的答案被选为“解决方案”——这是最糟糕的可能的答案恕我直言——但就是这样。
  • 我当然同意你的观点,最糟糕的答案是被选为“解决方案”的答案。但正如你所说,就是这样。
  • @JamesKanze,好吧,这是我的选择;-) 但是(正如我在接受它时评论的那样)我觉得它是“最好的”答案,因为我从中学到了最多的东西:使用 boost::in_place 和提升::可选。 “可怕的黑客”正是如此,我不会考虑它。 C++11 不是一个选项,所以这个答案对我来说还没有——有用,而且 C++03 中小“dosomething”函数的扩散不是我想要的考虑一种“干净”的方法。
【解决方案4】:

您的解决方案确实没有问题,尽管作为 其他人已经提到过,如果你会更易读 使用条件运算符而不是 if。但是你应该 考虑重构的可能性。如果你把所有因素都考虑在内 将对象使用到单独的函数中的代码 (通过引用获取对象),然后是:

if ( condition ) {
    Object a( p1 );
    doWhatever( a );
} else {
    Object a( p2, p3, p4 );
    doWhatever( a );
}

可能更可取(或者不是——我认为没有 关于在这两者之间进行选择的“正确”答案)。

【讨论】:

    【解决方案5】:

    我没有看到 复杂性...如果您需要基于声明指针的 if 条件有效地构造并使用 new 是您唯一的选择。您不一定需要做的是:

    1. 使用 scoped_ptr(虽然这通常是个好主意)
    2. 在“主”代码的 if 中有构造函数。您的案例是工厂的典型用例(参见例如http://en.wikipedia.org/wiki/Factory_method_pattern)。

    编辑:在第二句中添加了“有效地”。

    【讨论】:

    • 工厂的好声音。
    • 好吧,如果没有上下文,if-else 可能就足够了,而工厂则是矫枉过正......但很少有好的设计!这可能是一个很好的学习机会。
    • -1 “如果您需要基于声明指针的 if 条件进行构造,并且使用 new 是您唯一的选择”是不正确的。
    • 右阿尔夫。添加“高效”(意思是只构造一次)以使其正确。
    • @Epikorus:一般来说,动态分配效率很低。所以我担心添加“有效”这个词的编辑只会让事情变得更糟。
    【解决方案6】:

    我认为你的代码没问题。

    您可能只想考虑 条件运算符 ? :

    boost::scoped_ptr<Object> a( condition ? new Object(p1) : new Object(p2,p3,p4) );
    a->doSomething();
    

    【讨论】:

    • @LightnessRacesinOrbit:愚蠢的评论做得很好。我认为可读性(或缺乏)在旁观者的眼中。
    • 为什么要骂人?请不要那样做。
    • 真的,谁是那些为谁最擅长侮辱或谁做过侮辱而投票的人。很傻。无论如何,重新回答,动态分配在 C++ 中是相当高的成本,不能轻易发生,尤其是当存在简单的替代方案时,所以我不会这样做
    • 条件运算符当然提高了这里的可读性,但并没有解决他的具体问题,因为它还需要支持复制。
    【解决方案7】:

    所以,这里有一个快速的技巧来完成这项工作,而无需手动构造对象。相反,我创建了一个Deferred&lt;T&gt; 模板,它表示自动存储中的一个对象,该对象的构造被延迟(并且可能永远不会发生)。

    Deferred 中的 buff 应替换为 union,因为这将处理对齐问题(假设您有 C++11 功能支持)。当您调用get() 时断言constructed 为真可能是个好主意。

    一旦你构造了你的对象,你可以隐式地将你的Deferred&lt;T&gt; 转换为T&amp;,然后使用T&amp; 作为延迟构造的T 的别名。

    理论上,如果你能证明它总是会被构造,你可以取消constructedbool,但我建议不要这样做。除此之外,这应该几乎与您可以完成的一样有效。对于 C++11 union 案例,它甚至可能符合标准。

    哦,是的,应该通过完美转发来增强它。

    #include <utility>
    
    // does not handle alignment issues:
    template<typename T>
    struct Deferred {
       Deferred():constructed(false) {}
      operator T&() { return get(); }
      T& get() { return *reinterpret_cast<T*>(&buff[0]); }
      template<typename... Args>
      T& construct( Args... args ) {
        new(&buff[0]) T(args...);
        constructed = true;
        return get();
      }
      ~Deferred() {
        if (constructed) {
          get().~T();
        }
      }
    private:
      bool constructed;
      char buff[sizeof(T)];
    };
    
    #include <iostream>
    
    struct Object {
      bool is_int;
      Object( int x ):is_int(true) {}
      Object( double d ):is_int(false) {}
      ~Object() {
        std::cout << "~Object("<<is_int<<") destroyed\n";
      }
    };
    
    enum which_test {
      as_int,
      as_double,
      do_not,
    };
    void test(which_test v) {
      std::cout << v << "\n";
      Deferred<Object> o;
      if(v==as_int) {
        o.construct( 7 );
      } else if (v==as_double) {
        o.construct( 7.0 );
      } else {
      }
    }
    
    int main() {
      test(as_int);
      test(as_double);
      test(do_not);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-10-11
      • 2013-02-23
      • 1970-01-01
      • 2016-12-23
      • 1970-01-01
      • 1970-01-01
      • 2013-01-03
      • 1970-01-01
      相关资源
      最近更新 更多