在不指定派生类的情况下声明对象

时间:2019-08-23 19:36:17

标签: c++ inheritance types declare

有一个基类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;”到更一般的东西。预先感谢。

6 个答案:

答案 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;

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

...,但您知道它仅是DerivedADerivedB

您可以使用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内部发生的事情。