【问题标题】:C++ How to conditionally initialize a template class object?C ++如何有条件地初始化模板类对象?
【发布时间】:2021-08-20 05:44:45
【问题描述】:

我有一个模板类

template <typename T>
class myClass
{
   T variable;
....
};

我想创建一个 myClass 的对象,根据某些条件,该对象应该包含一个字符串或 int 。我如何有条件地初始化如下:

if (condition1)
  myClass<userDefinedType1> x;
else
  myClass<userDefinedType2> x;

我不认为我上面写的代码是可能的,但问题是在编译时我不知道类型应该是userDefinedType1 还是userDefinedType2。当我只能在运行时根据用户输入知道类型时,如何初始化模板类对象?

【问题讨论】:

  • 从通用基类继承、多态性和智能指针动态分配?
  • 我认为标题(和关键字)具有误导性。他想知道用于“求和类型”的方法,并且所讨论的组件类型恰好是同一模板的不同实例,这与问题无关紧要。

标签: c++ templates


【解决方案1】:

如果直到运行时才知道问题的类型,那么您的选择是有限的。

如果可能的实例化数量很少/简单并且您使用 或更高版本,则一种选择是只使用std::variant

using MyClass = std::variant<std::monostate, myClass<userDefinedType1>,myClass<userDefinedType2>>;

auto v = MyClass{}; // create one without a value to start

if (condition) {
  v = myClass<userDefinedType1>{};
} else {
  v = myClass<userDefinedType2>{};
}

std::variant 负责确保不发生不正确的访问(这可以防止违反严格别名的行为),并且它还将确保为正确实例化的类型调用非平凡的析构函数。这可以很容易地与visit 一起使用,假设需要调用的函数是同构定义的,因为您可以使用带有auto 参数的lambda,它将选择正确的类型:

std::visit([](auto& cls){
    cls.do_something();
}, v);

另一个可以使用的实用程序是std::any,用于类似目的:

auto v = std::any{};

if (condition) {
  v = myClass<userDefinedType1>{};
} else {
  v = myClass<userDefinedType2>{};
}

std::any 的缺点是访问内部类型要复杂得多,需要使用std::any_cast 进行轮询。仍然可行,但不是很好


如果你的模板可能有一些你可以擦除的基类,polymorphism 是一个选择。这可能不适用于所有设计,但如果确实如此,您可以在构造类时简单地使用std::unique_ptr 将实例保存到基类。

class myClassBase {
    ...
    virtual auto common_api(...) -> void = 0;
    ...
};
template <typename T>
class myClass : public myClassBase {
    ...
    auto common_api(...) -> void override { ... }
    ...
};

...

auto v = std::unique_ptr<Base>{};
if (condition) {
    v = std::make_unique<myClass<userDefinedType1>>(...);
} else {
    v = std::make_unique<myClass<userDefinedType2>>(...);
}


最后一个真正的选择是执行某种形式的手动类型擦除,您可以在其中创建一个擦除模板参数的包装类。此解决方案不是通用的,并且将高度特定于类本身,以了解如何擦除它。这可能涉及一些简单的东西,例如擦除实现中的类型的多态包装器,或者它可能更复杂,例如执行更多工作的绑定函数指针。

【讨论】:

  • get() 函数如何与基派生类方法一起使用?例如,如果我有一个函数T getVar(){ return Var; },我是否需要为基类中 T 可能具有的每种可能类型声明一个虚函数?
  • 不幸的是,这是通过继承实现这一目标的唯一方法,因为您不能将函数template 设为virtual。在这种情况下,您最好改用variant——因为当您调用std::visit 时,您将获得所要求的确切类型。或者,如果您的 T 类型都满足某种形式的属性,您也许可以通过其他方式擦除 T 类型本身。没有更多细节很难说什么是最好的
【解决方案2】:

啊,模板元编程 (TMP) 的疯狂世界。由于constexpr 等原因,现在变得容易多了,但是要对班级成员进行编程,您仍然需要走老路。

您必须通过从基类继承来生成您的成员。

该基类可以是一个模板,包括一个花哨的条件 TMP 构造。您可以在一些标准库类中看到这一点。

嗯,你想要 same 数据成员 (x),只是有不同的类型。好吧,这更容易:将其设为T2 类型。这可以使用模板的东西生成。像这样的:

using T2 = std::conditional_t<condition1, userDefinedType1, userDefinedType2>;
myClass<T2> x;

在编译时我不知道。

嗯,那是另一个故事了。您不能在编译时更改生成的代码except

你想要一个变量x 可以容纳两种类型中的任何一种吗?

这可以使用面向对象的技术,具有具有不同派生类型的通用多态基类。

或者,您可以使用std::variant

Ozger 的回答显示的(原始)union 基本上是同一想法的低级未经检查的版本。在旧版本的 C++ 中,这样的 union 无法保存具有构造函数和析构函数的类型,但现在它们可以了,尽管您需要付出很多低级的努力。它基本上是使编写variant 更简单的语言后端。

【讨论】:

  • OP 表示编译时条件未知。 std::conditional_t 需要编译时条件。
【解决方案3】:

您可以使用联合。

union U {
    myClass<userDefinedType1> a;
    myClass<userDefinedType2> b;
};

U u;
if (condition1)
  u.a = myClass<userDefinedType1>();
else
  u.b = myClass<userDefinedType2>();

如 cmets 中所述,仅当 myClass 具有普通析构函数时才会编译,包括将破坏 myClass 中 T 的隐式析构函数。

【讨论】:

  • 如果myClass 有一个重要的析构函数,这将不起作用。
  • @Human-Compiler not since C++11。虽然您必须编写联合的析构函数来销毁正确的成员,这可能有点令人困惑。只需使用std::variant
  • @JDługosz 即使在 C++11 中,您也必须手动定义析构函数 (~U()),因为如果它包含非平凡类型,它就是 implicitly deleted。所以上面的代码甚至不会按原样编译,除非析构函数是微不足道的(这是在手动调用析构函数以避免UB之前)
  • 啊,对“这不起作用”是指帖子中的代码无法编译,而不是“工会不能有这样的成员”。
【解决方案4】:

除非您需要存储x 以供将来使用, 你也可以制作适用于x模板的代码,然后你的分支看起来像:

template <typename T> void foo(T);

// ...

if (condition1) {
    myClass<userDefinedType1> x;
    foo(x);
} else {
    myClass<userDefinedType2> x;
    foo(x);
}

你甚至可以在函数模板中移动x,所以代码是:

template <typename T> void bar(T)
{
    myClass<T> x;
    // ...
}

// ...

if (condition1) {
    bar<userDefinedType1>();
} else {
    bar<userDefinedType2>();
}

如果您需要存储x 以供将来使用,您需要使用std::variant、多态基类或类型擦除。

【讨论】:

    【解决方案5】:

    我们不能只动态地创建类对象吗?像这样的:

    if (condition1)
      myClass<userDefinedType1>* x = new myclass<userDefined1>;
    else
      myClass<userDefinedType2>* x = new myclass<userDefined2>;
    

    请告诉我这种方法有什么问题

    【讨论】:

    • OP 正在寻求一种使用单一对象类型的方法,该对象类型可以存储两种不同类型中的任一。您的答案是构造两种不同且不相关的指针类型(因为模板的每个实例化都是唯一类型)。动态对象一种可能的解决方案——但这需要多态性和/或类型擦除——但是在使用模板时基类并不总是可行的。此外,智能指针和 C++11 已经存在了 10 年。手动使用带有常规指针的new 的充分理由
    • @Human-Compiler 感谢您的信息,这对我来说是一些新东西,但我想我明白了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-02
    • 1970-01-01
    • 1970-01-01
    • 2018-08-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多