有一个基类Base和两个可能的派生类DerivedA和DerivedB。如何在不指定(在声明时)指定两个派生类中的哪个将使用变量的情况下声明变量?
我尝试了以下示例:
#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;
};
编译器将引发错误,提示“错误:'struct Base'没有名为'x'的成员(或分别为'y')。
一个简单的解决方案是在两种情况下都使用单独的myObjs.push_back调用在所有if(a == 0)/ else语句中包含所有内容。但是,我对能够保持灵活性的解决方案很感兴趣,最好通过例如仅更改行“ Base TestObj;”到更一般的东西。预先感谢。
答案 0 :(得分:2)
“如何在不指定(在声明时)指定两个派生类中的哪个使用派生类的情况下声明变量?” - 你不能。
在C ++中,每个变量在声明时必须具有单个类型。没办法。
您可以,但是,可以使用指向基类的指针。这样可以接受分配任何派生类型的功能,并可用于通过虚拟/动态调度来调用派生类型的函数。
答案 1 :(得分:2)
如果要保持流程相同,则只需将TestObj
设为std::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);
}
答案 2 :(得分:2)
我想您需要这样的东西:
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));
答案 3 :(得分:2)
如果愿意将Test
用作类模板,则可以根据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;
答案 4 :(得分:0)
您可以使代码更简洁:
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;
};
答案 5 :(得分:0)
您不能精确地做您想做的事情:声明变量意味着指定其类型;并且如果您希望变量的类型为DerivedA或DerivedB,并在运行时做出决定-您就不能拥有它。
但是一切都不会丢失!
您可以在生成该类型的编译器中基于元计算来定义类型。这是@RSahu's answer:
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
的类型。
另一个想法是,不存储指向值的指针,而是将不透明的lambda对象(不包含指针或任何东西)存储在myObjs
(将变成myObjActions
之类的东西中),然后调用这些lambda必要时。所有这些lambda都需要具有相同的类型! ...因此从某种意义上讲,您只会将问题归结到lambda内部发生的事情。