条件变量声明

6

我来自Python,对于在C++中管理类型有些困惑。在Python中,我可以这样做:

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

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

typedef map<long, C1> TP1;

把 A 转换成 B:
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++中正确的方法?

提前致谢。


你在处理变量x时做了什么?顺便说一下,结构体C1和C2是相同的。 - BЈовић
如果您不知道x的类型,那么如何处理x[key],因为它可以是C1C2类型,每种类型都有不同的成员集合(且它们的名称不同)。 - Nawaz
@Nawaz 的 keylong 类型。 - Vijay Mathew
@Vijay: 我知道。但是 x[key] 的类型是 C1C2 中的一个。 - Nawaz
4
在C++中,没有真正“正确”的方法来完成你想做的事情。当然,你可以找到一些类型系统的 hack(例如,你可以在map中存储指向基类的指针而不是对象,使TP1和TP2相同),但我建议避免这样做。它引入的新问题比解决的问题还多。如果一种思路完全违反了语言的内在设计,那么要么使用错误的语言,要么重新考虑方法(例如,使用Python可能是一个完全有效的解决方案)。 - Damon
7个回答

7

如果条件在编译时就已知,您可以使用 std::conditional。这在通用代码中非常有用。

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

(where the test is made-up; here we're testing whether T is a pointer type or not)
如果条件直到运行时才能确定,则需要某种形式的动态多态性。C++中典型的这种多态性实例是子类型、boost::variant,或者在必要时可以使用boost::any。选择哪一个以及如何应用取决于您的总体设计; 我们了解得还不够。
*:很可能不会选择boost::any

你有没有可能提供一个使用 boost::variant 的例子来说明这个目的? - abcd
我只想指出,现在C++标准为这些boost类型定义了替代方案。 - ljleb

2
如果您需要两种不同的类型,最好的做法是(假设这些类相似并具有一些相似的成员函数),创建一个抽象类,例如CBase(参见http://www.cplusplus.com/doc/tutorial/polymorphism/),然后定义该抽象类的两个子类C1和C2。
现在,您可以将代码编写如下:
CBase *x;
if (condition) {
  x = new C1();
} else {
  x = new C2();
}

如果您无法将C1和C2提取到一个共同的抽象类中,那么您将需要两个不同的变量,并且condition充当标志,以便在后面您可以知道哪个变量已被填充,以及应该使用哪种结构。


他问 TP1TP2 分别代表什么。你的解决方案并没有解决他的问题。 - Nawaz
我建议完全重新设计这些类。如果他需要将两个不同类的对象分配给同一个变量,并且C1和C2可以抽象成一个抽象类,那么这是更加简洁的方法。 - Susam Pal
Nawaz,你是对的。我意识到我的回答并不是很有用,因为他有两个不同的地图而不是类。我只能建议重新设计这些类,因为似乎没有什么干净的解决方法。 - Susam Pal

2

您有几种选择。如果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.

请注意,为了保持初始序列“common”,您需要更改名称,以便结构的两个版本中第二个成员(descr/other)具有相同的名称。
如果它们不是POD,则可以使用继承来提供通用类型。
然而,C++没有Python著名的“鸭子类型”的直接对应物。虽然模板提供了类型擦除(至少在某种程度上),但你最终会得到与在Python中所做的相反的结果。你允许代码处理具有共同语法的两种不同类型。然而,这是不同的,因为编译器需要能够在编译时解析任何特定模板使用的实际类型,而不仅仅是运行时。
如果您确实需要在运行时解析类型,则模板可能不起作用 - 您可能需要使用联合或基类。

2
尽管可能有一些方法可以实现,但它们大多是棘手的且不可维护,正如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;
}

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*/
}

他问TP1TP2代表什么。你的解决方案并没有解决他的问题。 - Nawaz

0
为了通过一个公共变量使用两种不同的类型,这些类型必须有一个共同的基类。由于你拥有的是两种不同的类型,你无法改变它们,并且它们没有共同的基类,因此你需要某种鸭子类型。在C++中,只有模板使用鸭子类型:一种解决方案是将条件后面的所有代码移动到一个单独的函数模板中,将结果传递给该函数模板,然后编写类似以下内容的代码:
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> ));

0

你试图在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和CCircle),那么一个共同的基类是有意义的。如果它们没有关系(比如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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接