在运行时有条件地定义将在 C++ 代码的其余部分中使用 3 个类中的哪一个

Conditionally define, during runtime, which of 3 classes will be used in the rest of C++ code

提问人:blipblop 提问时间:11/24/2016 最后编辑:blipblop 更新时间:11/25/2016 访问量:1229

问:

这是我尝试在C++中做的事情。从我使用的外部库中,我有 3 个类,它们的公共函数是相同的。我想在运行时开始时决定在我的其余代码中使用哪一个,具体取决于用户的硬件配置。MyClass1MyClass2MyClass3

为了更好地说明我的意思,让我举一个我知道行不通的例子。如果可以有条件地在运行时定义一个,我试图实现的目标如下所示:typedef

const int x = GetX(); //where GetX() is any function that calculates the value of x
typedef std::conditional<x > 0, MyClass1,
            std::conditional< x < 0, MyClass2,
                MyClass3>::type>::type TheClass;

因此,在代码的其余部分,我只会引用 ,这样一来,它是别名还是 。TheClassMyClass1MyClass2MyClass3

但是,上面的代码当然不起作用,因为当从运行时开始时执行的函数计算其值时,然后抱怨这不是常量。这是有道理的,因为无法在运行时定义。xstd::conditionalxtypedef

所以,我的问题是:有没有办法实现我正在尝试做的事情(不是因为我知道它不能在运行时定义)?请记住,并且由图书馆外部提供,并且以我无法轻易更改它们的方式提供。typedefMyClass1MyClass2MyClass3

C++ 条件语句 别名 typedef 使用

评论

2赞 Peter 11/24/2016
不可能有一个在运行时之前未知的,因为一个是编译时的概念。查找多态性。typedeftypedef
0赞 blipblop 11/24/2016
@Peter 哦,是的,我知道。我实际上是想问,如果没有正因为如此,我怎么能实现这个想法。我更新了答案以更好地反映它。typedef
0赞 Jon Cage 11/24/2016
这三个类是否都继承自一个公共基类?
0赞 blipblop 11/24/2016
@JonCage 在他们所属的图书馆中,不,他们不属于。但我想改变这一点,让它们从一个抽象的、空的类继承过来就很容易了
0赞 Peter 11/24/2016
假设您已经声明它们具有相同的接口,那么让抽象基类提供该接口会更容易。如果可以更改它们,使它们从抽象空基继承,也可以将它们更改为从抽象多态基继承(每个具体类根据需要专用的虚函数)。如果无法做到这一点,每个派生类的工作都可以与库中的类进行交互。

答:

1赞 Jon Cage 11/24/2016 #1

如果这三个都继承自一个公共类,则可以执行以下操作:

class BaseClass
{
    virtual int GetSomething() = 0;
};

class Class1 : public BaseClass
{
    virtual int GetSomething() override
    {
        return 1;
    }
};

class Class2 : public BaseClass
{
    virtual int GetSomething() override
    {
        return 2;
    }
};

class TheClass : public BaseClass
{
    virtual int GetSomething() override
    {
        return 3;
    }
};


BaseClass* classInterface;

const int x = GetX(); //where GetX() is any function that calculates the value of x
if (x > 0) { classInterface = new MyClass1(); }
elseif (x < 0) { classInterface = new MyClass2(); }
else { classInterface = new TheClass(); }

printf(classInterface->GetSometing());

如果没有,您需要包装它们并执行如下操作:

class Class1
{
    int GetSomething()
    {
        return 1;
    }
};

class Class2
{
    int GetSomething()
    {
        return 2;
    }
};

class TheClass
{
    int GetSomething()
    {
        return 3;
    }
};

class BaseClass
{
    virtual int GetSomething() = 0;
};

class Class1Wrapper  : public BaseClass
{
    Class1 m_class;

    virtual int GetSomething() override
    {
        return m_class.GetSomething();
    }
};

class Class2Wrapper : public BaseClass
{
    Class2 m_class;

    virtual int GetSomething() override
    {
        return m_class.GetSomething();
    }
};

class TheClassWrapper : public BaseClass
{
    TheClass m_class;
    virtual int GetSomething() override
    {
        return m_class.GetSomething();
    }
};    

BaseClass* classInterface;

const int x = GetX(); //where GetX() is any function that calculates the value of x
if (x > 0) { classInterface = new MyClass1Wrapper(); }
elseif (x < 0) { classInterface = new MyClass2Wrapper(); }
else { classInterface = new TheClassWrapper(); }

printf(classInterface->GetSometing());

[编辑] ..如果你想节省重做 if 语句的时间,你可以创建一个静态方法来生成一个新的基类:

static BaseClass* GetClass()
{
    BaseClass* classInterface;
    const int x = GetX(); //where GetX() is any function that calculates the value of x
    if (x > 0) { classInterface = new MyClass1Wrapper(); }
    elseif (x < 0) { classInterface = new MyClass2Wrapper; }
    else { classInterface = new TheClassWrapper; }
    return classInterface;
}

..然后这样称呼它:

BaseClass* classInterface = GetClass();

// Do something

delete classInterface;

评论

0赞 blipblop 11/24/2016
感谢您的详细回答。我看到的唯一问题是,由于它实际上并没有用一个新的类别名替换三个原始类,而是使用 的实例,那么每当我创建 BaseClass 的新实例时,我都必须进行检查并为新实例分配一个。有没有办法克服这个问题?我的意思是,要使所有创建的实例自动检查的值,然后相应地将自己初始化为正确的子类?BaseClassBaseClassif-elseif-elsenew MyClassWraperBaseClassx
2赞 Stefano Falasca 11/25/2016 #2

我能看到的唯一解决方案是使用模板为您生成代码。模语法错误,只要您将 x 转换为编译器已知值,基于您的解决方案就会起作用。诀窍是将所有使用 typedef 的代码包装在具有整数作为模板参数的模板函数/类中,如下所示:std::conditional

template <int x>
void myMain(){
    using TheClass = typename std::conditional<x == 0, MyClass1, MyClass2>::type;

然后,你要确保编译所有变体(在我的示例中为 0 和非零),为此,你要显式调用两者,比如说,,如:myMain<0>()myMain<1>()

if(x == 0){
    myMain<0>();
}
else{
    myMain<1>();
}

现在,您已经将条件转换为在运行时进行评估的内容,但您已经编译了这两种情况的代码,并且可以根据自己的心意执行每种情况(或两者)。

这样做的缺点是将使用该类的任何内容制作成模板或由模板调用的内容。除了“调度”点之外,我建议在类型而不是整数上成为模板(参见示例中的函数);这更好地表达了这样一个事实,即您的代码可以处理要实例化它的所有类型。如果要确保只能使用您感兴趣的三个类实例化函数,则应考虑使用 CRTP 模式(奇怪的是重复出现的模板参数)。doSomethingWithClass

另一方面,这有一个优点(相对于基于多态性的另一个答案),您可以使用堆栈而不是堆。

你可以在这里找到一个工作的例子。

希望对你有所帮助。

1赞 Trevor Hickey 11/25/2016 #3

若要在编译时执行此操作,GetX 函数必须为 。 使用比较运算符也与模板语法冲突。您需要为小于和大于以下内容提供 consexpr 函数:constexpr

constexpr int GetX(){ return 0;}
constexpr bool IsGreater(int x, int y) { return x > y;}
constexpr bool IsLess(int x, int y) { return x < y;}

typedef std::conditional<IsGreater(GetX(),0), MyClass1,
            std::conditional<IsLess(GetX(),0), MyClass2,
                MyClass3>::type>::type TheClass;

如果无法生成 constexpr(因为该值是在运行时确定的),
则您正在寻找 Sum 类型。它们在函数式编程语言中很常见,C++ 现在以 std::variant 的形式支持库。
GetX()

您的示例代码可以转换为以下内容:

int main(){

  //a type that can be 1 of 3 other types
  std::variant<MyClass1,MyClass2,MyClass3> TheClass;

  //decide what type it should be at runtime.
  const int x = GetX();
  if      (x > 0) { TheClass = MyClass1(); }
  else if (x < 0) { TheClass = MyClass2(); }
  else            { TheClass = MyClass3(); }
}

在这里,您将在运行时决定类型。
您可以继续使用模式匹配来评估所持有的类型。

评论

0赞 blipblop 11/25/2016
谢谢你的回答。事实上,我不能制作 constexpr,因为正如我所说,它是在运行时确定的。但是,您的第二个解决方案似乎是理想的情况。你知道哪些编译器已经有这样的C++17功能吗?似乎 VS2015 仍然没有,所以我无法测试它。GetX()
0赞 Trevor Hickey 11/25/2016
std::variant 是相当新的。最好的办法是引入 boost::variant。
0赞 blipblop 11/25/2016
我用boost::variant尝试过。此解决方案的问题如下。首先,这里不再是一个类,而是一个变量。对于要创建的每个变量,我必须执行 if-elseif-else 并分配所需的 MyClass。这可以通过一种方法来解决。其次,访问变量变得非常麻烦:.第三,这种访问方式违背了目的,因为我必须提前知道要查找哪种类型(例如,我刚才给出的 getter 示例中的 MyClass2),即在编译时。TheClassboost::get<MyClass2>(TheClass)