条件变量声明

Conditional variable declaration

本文关键字:声明 变量 条件      更新时间:2023-10-16

我来自Python,我在c++中管理类型有一些问题。在Python中,我可以这样做:

if condition_is_true:
    x=A()
else:
    x=B()

和在程序的其余部分中,我可以使用x而不必关心x的类型,只要我使用具有相同名称和参数的方法和成员变量(A和B不一定具有相同的基类)。现在在我的c++代码中,类型A对应于

typedef map<long, C1> TP1;

and B to:

typedef map<long, C2> TP2;

地点:

typedef struct C1
{
    char* code;
    char* descr;
    int x;
...
}

typedef struct C2
{
    char* code;
    char* other;
    int x;
...
}

C1和C2有相似的成员,在我所说的代码部分,我只需要使用具有相同名称/类型的

我想这样做:

if (condition==true)
{
    TP1 x;
}
else
{
    TP2 x;
}

c++中正确的方法是什么?

提前致谢

如果在编译时知道该条件,则可以使用std::conditional。这在通用代码中很有用。

typedef std::conditional<
    std::is_pointer<T>::value
    , TP1
    , TP2
>::type map_type;
map_type x;

(其中测试是组成的;这里我们测试T是否是指针类型)

如果直到运行时才知道条件,则需要某种形式的动态多态性。在c++中,这种多态性的典型实例是subtyping, boost::variant,或者在紧要关头,boost::any。你应该选择哪一个以及如何应用它取决于你的总体设计;我们知道的还不够。

*: very likely not to boost::any .

你有几个选择。如果C1和C2都是POD类型,则可以使用联合,它允许访问公共初始序列:

struct C1 { 
    // ....
};
struct C2 { 
    // ...
};
union TP {
    C1 c1;
    C2 c2;
};
union TP x;
std::cout << x.c1.code; // doesn't matter if `code` was written via c1 or c2.

请注意,为了保持初始序列"公共",您确实需要更改名称,以便第二个成员(descr/other)在两个版本的结构体中具有相同的名称。

如果它们不是pod,你可以使用继承来给你一个通用的类型。

然而,c++并没有直接对应Python著名的"duck typing"。虽然模板提供了类型擦除(至少在某种程度上),但您最终会得到与Python中所做的相反的结果。在处理变量时,两种类型之间不会发生变化,而是允许代码处理具有相同语法的两种不同类型。然而,这是不同的,因为它要求编译器能够在编译时解析用于任何特定模板的实际类型,而不仅仅是在运行时。

如果您确实需要在运行时解析类型,那么模板可能不起作用——您可能需要使用联合或基类。

如果您确实需要两个不同的类型,最好的方法是(假设类相似并且有一些相似的成员函数)有一个抽象类,比如CBase(参见http://www.cplusplus.com/doc/tutorial/polymorphism/),然后定义这个抽象类的两个子类C1和C2。

现在你的代码可以这样写:
CBase *x;
if (condition) {
  x = new C1();
} else {
  x = new C2();
}

如果你不能将C1和C2抽象成一个共同的抽象类,那么你就需要两个不同的变量,condition就像你的标志一样,使用它你可以知道哪个变量已经被填充,哪个结构要使用。

尽管可能有一些方法可以做到这一点,但正如Damon提到的,它们大多很棘手,而且不可维护。

建议使用模板函数。您真正想要的是访问不同类的相同成员/函数。在模板函数中,只要该类型提供模板中使用的操作,就可以访问"通用类型"的对象。

例如,在您的示例中,您可以简单地将公共部分提取到模板函数中,如下所示:

struct TP1
{
  // common part
  int i;
  int j;
  // different part
  float f;
};
struct TP2
{
  // common part
  int i;
  int j;
  // different part
  double d;
};
template<typename CType>
void f(CType a)
{
  // as long as CType has variable i, j
  cout << a.i << endl;
  cout << a.j << endl;
}
int main(int argc, char* argv[])
{
  bool choice;
  // get a choice from console during runtime
  cin >> choice;
  if (choice)
  {
    TP1 x = {0, 0};
    f(x);
  }
  else
  {
    TP2 x = {1, 1};
    f(x);
  }
  return 0;
}

我认为你可以通过运行时多态性来做到这一点。

class C_Base { /*all common variables*/ } ;
class C1 : public C_Base { ... };
class C2 : public C_Base { ... };
typedef map<long, C_Base *> TP;
{
...
    TP x;
    if (condition)
        /*use x as if values are C1 * */
    else
        /*other way round*/
}

为了使用两种不同的类型通过一个共同的变量,该类型必须有一个公共基类。因为你拥有的是两种不同的你不能改变的类型,没有公共基类的类型,你需要一些鸭子打字。在c++中,只有模板才使用duck输入:一种解决方案是将所有代码移到条件放入一个单独的函数模板中,将结果,然后写入如下内容:

if ( condition_is_true )
    wrapped_code( A() );
else
    wrapped_code( B() );

根据条件后面的代码,这可能是或多或少方便。

一个更通用的替代方法是创建类层次结构来包装地图。这个解决方案有点冗长,但非常简单:只需定义一个基用您想要的接口初始化,例如:

class Map
{
public:
    virtual ~Map() {}
    virtual std::string getCode( long index ) const = 0;
    virtual std::string getDescr( long index ) const = 0;
    virtual int getX( long index ) const = 0;
};

,然后是由它派生的模板:

template <typename T>   // Constraint: T must have accessible members code, other and x
class ConcreteMap : public Map
{
    std::map <long, T> myData;
public:
    virtual std::string getCode( long index ) const
    {
        return myData[index].code;
    }
    virtual std::string getDescr( long index ) const
    {
        return myData[index].descr;
    }
    virtual int getX( long index ) const
    {
        return myData[index].x;
    }
};

你的if变成:

std::unique_ptr<Map> x = (condition_is_true
                          ? std::unique_ptr<Map>( new ConcreteMap<C1> )
                          : std::unique_ptr<Map>( new ConcreteMap<C2> ));

你想做的在c++中是不可能的。c++中的变量有固定的类型,在编译时定义,不能在运行时改变类型。但是c++确实提供了多态性(看起来像动态类型),它允许派生类型实现基类功能,但是访问特定类型方法的唯一方法是将类型绑定到基类,如果您将类型绑定到派生类型,那么您只能调用该类型的实现*:

class Base
{
public: virtual void Func () = 0;
};
class C1 : public Base
{
public: virtual void Func () {}
};
class C2 : public Base
{
public: virtual void Func () {}
};
void SomeFunc ()
{
  C1 *c1 = new C1;
  C2 *c2 = new C2;
  Base *b;
  b = c1;
  b->Func (); // calls C1::Func
  b = c2;
  b->Func (); // calls C2::Func
}

看起来b已经改变了类型,但它的实际类型保持不变,它始终是Base *,它只能被分配值c1c2,因为它们共享一个公共基类Base

Base *b = new C1;
C1 *c1 = dynamic_cast <C1 *> (b);

但是它需要dynamic_cast,这需要RTTI(运行时类型信息),它提供了一种方法来检查b实际上指向C1类型。如果您要执行以下操作:

Base *b = new C2;
C1 *c1 = dynamic_cast <C1 *> (b);

c1将是空指针,而不是b。但是C1和C2仍然必须有一个共同的基类才能工作。

class Base {....}
class C1 : public Base {....}
class C2 {....} // not derived from Base!
Base *b = new C2; // no way to convert C2 to Base!
C2 *c2 = new C2;
b = dynamic_cast <Base *> (c2); // still won't work, C2 not a Base
b = new C1; // fine, C1 is a Base
C1 *c1 = new C1;
b = c1; // again, fine
c1 = dynamic_cast <C1 *> (b); // fine, C1 is a derived type of Base, this will work
c2 = dynamic_cast <C2 *> (b); // won't work, C2 is not a derived type of Base

如果C1和C2是相关的(比如,CSquare和circle),那么一个公共基类是有意义的。如果它们不相关(比如,CRoad和CFood),那么一个公共基类将无济于事(它可以做到,但它不是很合乎逻辑)。前一种方法(公共基类)在其他答案中有很好的描述。如果您需要执行后者,那么您可能需要重新考虑如何构建代码以允许您执行前者。

如果你能详细说明你想用x做什么,这会有所帮助。既然x是一个容器,你只是想做容器相关的操作吗?

    当然,在c++中事情从来没有那么容易,有很多事情会混淆这个问题。例如,派生类型可以私下实现公共基类虚方法:

的例子:

class B
{
public:
  virtual void F () = 0;
};
class C : public B
{
private:
  virtual void F () { .... }
};
C *c = new C;
B *b = c;
b->F (); // OK
c->F (); // error