声明派生 class 的对象而不指定它

Declare object of derived class without specifying it

有一个base-class Base和两个可能的派生classes DerivedA和DerivedB。如何在不指定(在声明时)两个派生的 classes 中的哪一个将被使用的情况下声明变量?

我试过下面的例子:

#include <iostream>
#include <vector>
#include <memory>
using namespace std;
#include <stdlib.h>


struct Base
{
    int base = 0;
};

struct DerivedA : Base
{
    int x = 1;
};

struct DerivedB : Base
{
    int y = 1;
};


class Test
{
public:
    Test(int a)
    {
    Base TestObj;
    if (a==0)
    {
        DerivedA TestObj; // intention: change type of TestObj to DerivedA
    }
    else
    {
        DerivedB TestObj; // intention: change type of TestObj to DerivedB
    }

    TestObj.base = 7;

    if (a==0)
    {
        TestObj.x = 2;
    }
    else
    {
        TestObj.y = 4;
    }

    myObjs.push_back(make_shared<Base>(TestObj));
    }
private:
    vector<shared_ptr<Base>> myObjs;

};

编译器将抛出一个错误,提示 "error: ‘struct Base’ has no member named ‘x’ "(或分别为 'y')。

一个简单的解决方案是将所有内容包含在第一个 if (a==0) / else 语句中,对这两种情况使用单独的 myObjs.push_back 调用。但是,我对可以保持更灵活的解决方案感兴趣,最好是通过例如仅将行 'Base TestObj;' 更改为更通用的内容。提前致谢。

"How can I declare a variable without specifying (at the point of declaration) which of the two derived classes will be in use?" - 你不能。

在 C++ 中,每个变量在声明时都必须有一个 单一 类型。没办法。

可以,但是,使用指向基class 的指针。这将接受分配任何派生类型,并可用于通过 virtual/dynamic 调度调用派生类型中的函数。

如果你想保持流程不变,那么你只需要开始 TestObjstd::shared_ptr<Base>。这将使代码

Test(int a)
{
    std::shared_ptr<Base> TestObj;
    if (a==0)
    {
        TestObj = std::make_shared<DerivedA>();
    }
    else
    {
        TestObj = std::make_shared<DerivedB>(); 
    }

    TestObj->base = 7;

    if (a==0)
    {
        static_cast<DerivedA&>(*TestObj).x = 2; // need the cast so you can set the member
    }
    else
    {
        static_cast<DerivedB&>(*TestObj).y = 4; // need the cast so you can set the member
    }

    myObjs.push_back(TestObj);
}

我猜你需要这样的东西:

std::shared_ptr<Base> TestObj;
if (a==0)
{
    TestObj = std::make_shared<DerivedA>();
}
else
{
    TestObj = std::make_shared<DerivedB>();
}

TestObj->base = 7;

if (a==0)
{
    static_cast<DerivedA*>(TestObj.get())->x = 2;
}
else
{
    static_cast<DerivedB*>(TestObj.get())->y = 4;
}

myObjs.push_back(std::move(TestObj));

如果您愿意将Test制作成class模板,您可以根据a的值选择对象类型。

template <int a>
class Test
{
   TypeSelector<a> TestObj;
   ...
};

哪里

template <int a> class TypeSelectorHelper;

template <> class TypeSelectorHelper<0>
{
   using type = DerivedA;
}

template <> class TypeSelectorHelper<1>
{
   using type = DerivedB;
}

template <int a> 
using TypeSelector = typename TypeSelectorHelper<a>::type;

您可以让您的代码更简洁一点:

class Test
{
  public:
    Test(int a) {
        std::shared_ptr<Base> TestObj;
        if (a == 0) {
            std::shared_ptr<DerivedA> TestA  = std::make_shared<DerivedA>();
            TestA->x = 2;
            TestObj = TestA;
        }
        else {
            std::shared_ptr<DerivedB> TestB = std::make_shared<DerivedB>();
            TestB->y = 4;
            TestObj = TestB;
        }
        TestObj->base = 7;
        myObjs.push_back(TestObj);
    }
  private:
    vector<shared_ptr<Base>> myObjs;
};

你不能完全做你想做的事:声明一个变量意味着指定它的类型;并且如果您希望变量具有 DerivedA 类型或 DerivedB 类型,并在 运行 时做出决定 - 您就不能那样做。

但并非一无所有!

如果可以在编译时确定类型

您可以在生成类型的编译器中基于元计算定义类型。这是 :

constexpr bool determine_if_we_need_derived_a() { /* ... compile-time computation ... */ }
using type_i_need = typename type_selector<determine_if_we_need_derived_a()>::type;

如果无法在编译时确定类型...

...但你知道它要么是 DerivedA 要么是 DerivedB 只有

您可以使用 std::variant:更高级、更安全、更好的 C 风格联合版本:

std::variant<DerivedA, DerivedB> get_derived(/*params here*/)
{
    // etc.
    if (condition) { return DerivedA( /* ... */); }
    else           { return DerivedB( /* ... */); }
}

// ...

auto derived_object = get_derived(arg1, arg2, /* etc. */);

但当然您之后需要使用 visitation,以便根据您的 derived_object.

中的实际类型应用适当的操作

...但是你什么都不知道

除了使用指针,您可能别无选择,就像@NathanOliver 和其他人建议的那样:

std::unique_ptr<Base> get_derived(/*params here*/)
{
    // etc.
    if (condition) { return std::make_unique<DerivedA>(/* ... */); }
    else           { return std::make_unique<DerivedB>(/* ... */); }
}

// ...

myObjs.emplace_back(std::move(get_derived(arg1, arg2, /* etc. */)));

...而且你感觉很活泼

你可以试试std::any。您可以在其中放入 任何类型任何类型 !现在,它不是 工作的正确抽象,但了解它很有趣。问题是,在检索值并使用它之前,您基本上需要知道 std::any 的类型。

另一个想法是,不是存储指向值的指针,而是在 myObjs 中存储一个不透明的 lambda 对象(没有指针或任何东西)(这将变成 myObjActions 或其他东西),然后调用必要时使用那些 lambda。不过,所有这些 lambda 都需要具有相同的类型! ...所以从某种意义上说,您只会将问题归咎于 lambda 中发生的事情。