是否可以在运行时切换到不同的基类构造函数?

时间:2017-04-28 13:08:59

标签: c++ c++11 inheritance constructor

假设我正在编写Derived并且必须继承Base,我无法控制它,并且有两个独立的构造函数和一个已删除的副本和移动构造函数:

struct Base {
    Base(int i);
    Base(const char *sz);
    Base(const Base&) = delete;
    Base(const Base&&) = delete;
};

struct Derived {
    Derived(bool init_with_string);
};

现在,根据another_param的值,我必须使用构造函数或其他函数初始化我的基类;如果C ++不那么严格,那就像是:

Derived::Derived(bool init_with_string) {
    if(init_with_string) {
        Base::Base("forty-two");
    } else {
        Base::Base(42);
    }
}

(这对于计算在直接表达式中传递给基类构造函数/字段初始化器的值很麻烦的所有情况也很有用,但我很讨厌)

不幸的是,即使我没有看到特定的codegen或对象模型阻碍这类事情,这也不是有效的C ++,我想不出简单的解决方法。

我有什么方法可以解决这个问题吗?

5 个答案:

答案 0 :(得分:11)

静态函数在这里可以正常工作

struct Base {
    Base(int i);
    Base(const char *sz);
    Base(const Base&) = delete;
    Base(const Base&&) = delete;
};

struct Derived : Base {
   using Base::Base;

   static Derived construct(bool with_string) {
      if(with_string) return { "forty-two" };
      return { 42 };
   }
};

请注意,这不需要移动,也不需要复制构造函数。如果你想把它作为一个本地,你需要将它绑定到一个引用,以避免移动它

auto &&w = Derived::construct(true);
auto &&wo = Derived::construct(false);

答案 1 :(得分:5)

不理想,但是当一个对象的初始化必须在代码块内发生时,我没有更好地建议自己使用这种技术:

// (Same base as your code)
#include <memory>

struct Derived : public Base {
    using Base::Base;
};

int main(int argc, char **argv)
{
    std::unique_ptr<Derived> pd;

    if ( argc == 2 )
    {
        pd = std::make_unique<Derived>(42);
    }
    else
    {
        pd = std::make_unique<Derived>("forty-two");
    }

    Derived &d = *pd;

   // use d, don't manually reset pd
}

答案 2 :(得分:1)

修改:以下方法非常接近你所使用的方法,使用了新的地方:

struct Derived3 : Base
{
    Derived3::Derived3(bool init_with_string) : Base(42)
    {
        if(init_with_string)
        {
            // in case of any resources would have been allocated:
            this->~Derived3();
            new(this) Derived3("forty-two");
        }
    }
private:
    using Base::Base;
};

首先,我用两种类型中的一种构造一个Base对象。如果类型条件不匹配,我需要再次销毁它,显式调用析构函数以避免未定义的行为(并防止在Base已分配内存的情况下发生泄漏)。之后,我们可以使用其他构造函数重新构建类。好吧,这就是这种方法的缺点,我们可能会徒劳地分配一些内存,只是为了再次释放它然后重新分配它!至少,在某些情况下,我们徒劳无功地创建了一个对象。

所以没有非加超解决方案,所以我将保留以前的方法:

目前提供两种方法,两种方法都不是你想要的,但是现在,我没有接近......

嗯,明显而简单的解决方案:

struct Derived1 : Base
{
    static Derived1* instance(bool init_with_string)
    {
        return init_with_string ? new Derived1("forty-two") : new Derived1(42);
    }
private: // or even not, then you can construct your derived classes directly...
    using Base::Base;
};
int main(int argc, char* argv[])
{
    Derived1* d1 = Derived1::instance(false);
}

模板变体:

struct Derived2 : Base
{
private:
    using Base::Base;
    template <bool>
    friend struct Derived2Maker;
};

template <bool InitWithString>
struct Derived2Maker : Derived2
{
    Derived2Maker() : Derived2(42) { }
};
template <>
struct Derived2Maker<true> : Derived2
{
    Derived2Maker() : Derived2("forty-two") { }
};
int main(int argc, char* argv[])
{
    Derived2* d2 = new Derived2Maker<false>();
}

缺点:在编译时必须知道布尔参数...

答案 3 :(得分:1)

这不是真正您正在寻找的答案,但是如果您可以将决策暴露给调用构造函数的代码,那么您可以使用tag-dispatch模板构造函数使用constexpr-if( bingo!)的通用lambda,如下所示:

#include <type_traits>
#include <memory>

struct Base {
    Base(int) {};
    Base(const char *) {};
    Base(const Base&) = delete;
    Base(const Base&&) = delete;
};

struct Derived : Base{
    static std::unique_ptr<Derived> make_unique(bool init_with_string);

private:
    template<typename init_with_string_t>
    Derived(init_with_string_t);
};

template<typename init_with_string_t>
Derived::Derived(init_with_string_t) : Base([]{if constexpr(init_with_string_t::value) return "forty-two"; else return 42;}())
{
}

std::unique_ptr<Derived> Derived::make_unique(bool init_with_string)
{
    if (init_with_string)
        return std::unique_ptr<Derived>(new Derived(std::true_type{}));
    else
        return std::unique_ptr<Derived>(new Derived(std::true_type{}));
}

int main()
{
    auto d1 =  Derived::make_unique(true);
    auto d2 =  Derived::make_unique(false);
}

Live demo on wandbox

这是一个C ++ 17重要的功能列表,所以clang 3.9或gcc 7.正如我在这里所做的那样,你可以用工厂包装tag-dispatch调用,如果你把它作为类的静态成员,您可以根据自己的喜好选择是使用字符串或私有静态类方法中的值进行初始化的逻辑,还是在工厂方法中内联。

我必须解决一些不太令人愉快的问题才能实现这个目标:

需要进行类型调度,因为即使我们可以使Derived的构造函数为:

template<bool init_with_string>
Derived::Derived() : Base([]{if constexpr(init_with_string) return "forty-two"; else return 42;}())
{
}

实际上是no way to explicitly specify template parameters to a constructortemplate deduction cannot deduce non-type template parameters。 (如果你有权访问范围内的constexpr值,你可能会做template<bool init_with_string = constexpr_bool_method()>之类的事情,但是如果你能做到这一点,你可以直接把它放在lambda中。

if constexpr lambda想法来自an answer to Equivalent ternary operator for constexpr if?,本来可以更好。

可悲的是,我们无法在工厂方法中使用std::make_unique来访问私有构造函数,因为friendship isn't transitive

答案 4 :(得分:-1)

老实说,你应该尊重Base的实施,让Derived有两个构造函数。

class Derived : Base {
    Derived(int i) : Base(i) {}
    Derived(const char *s) : Base(s) {}
}

然后正如Johannes Schaub所说,即使没有复制构造函数,实际上也可以使用初始化列表进行外部make_derived()