声明派生 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 调度调用派生类型中的函数。
如果你想保持流程不变,那么你只需要开始 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);
}
我猜你需要这样的东西:
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 中发生的事情。
有一个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 调度调用派生类型中的函数。
如果你想保持流程不变,那么你只需要开始 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);
}
我猜你需要这样的东西:
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 中发生的事情。