我来自Python,我在使用c ++管理类型时遇到了一些问题。在Python中,我可以这样做:
if condition_is_true:
x=A()
else:
x=B()
并且在程序的其余部分我可以使用x而不关心x的类型,因为我使用具有相同名称和参数的方法和成员变量(不必使A和B具有相同的基类)。 现在在我的C ++代码中,类型A对应于
typedef map<long, C1> TP1;
和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 ++中的正确方法是什么?
提前致谢
答案 0 :(得分:5)
如果在编译时已知条件,则可以使用std::conditional
。这在通用代码中很有用。
typedef std::conditional<
std::is_pointer<T>::value
, TP1
, TP2
>::type map_type;
map_type x;
(测试已经完成;这里我们测试T
是否是指针类型)
如果在运行时之前无法知道条件,则需要某种形式的动态多态。 C ++中这种多态性的典型实例是子类型,boost::variant
或推送时,boost::any
。您应该选择哪一个*以及如何应用它取决于您的总体设计;我们还不够了解。
*:很可能不为boost::any
。
答案 1 :(得分:2)
你有几个选择。如果C1和C2都是POD类型,则可以使用union,它允许访问公共初始序列:
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
)在两个版本的结构中具有相同的名称。 / p>
如果它们不是POD,您可以使用继承为您提供一个通用类型。
然而,C ++并没有与Python着名的“鸭子打字”直接对应。虽然模板提供了类型擦除(至少在某种程度上),但你最终会遇到与Python中所做的相反的事情。代替处理变量的两种类型之间的变化,您可以允许代码处理具有通用语法的两种不同类型。但这是不同的,因为它要求编译器能够在 compile 时解析与任何特定模板一起使用的实际类型,而不仅仅是运行时。如果你真的需要在运行时解析类型,那么模板可能不起作用 - 你可能需要使用union或基类。
答案 2 :(得分:2)
虽然可能有一些方法可以做到这一点,但就像达蒙所说的那样,它们大多是棘手而且难以维护。
我建议您使用模板功能。你真正想要的是访问不同类的相同成员/函数。在模板函数中,只要类型提供了在模板中使用的操作,就可以访问“常规类型”的对象。
例如,在您的情况下,您可以简单地将公共部分提取到这样的模板函数中。
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;
}
答案 3 :(得分:1)
如果你真的需要两种不同的类型,那么最好的做法是(假设类相似并且有一些类似的成员函数)来创建一个抽象类,比如CBase(参见http://www.cplusplus.com/doc/tutorial/polymorphism/)然后定义这个抽象类的两个子类C1和C2。
现在您的代码可以编写如下:
CBase *x;
if (condition) {
x = new C1();
} else {
x = new C2();
}
如果您无法将C1和C2抽象为一个公共抽象类,那么您将需要两个不同的变量,而condition
就像您的标志一样,您可以在以后知道哪个变量已被填充,使用哪种结构。
答案 4 :(得分: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*/
}
答案 5 :(得分:0)
为了通过公共变量使用两种不同的类型,类型 必须有一个共同的基类。既然你拥有的是两个不同的 您无法更改的类型,以及没有公共基类的类型, 你需要某种鸭子打字。在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> ));
答案 6 :(得分: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 *
,并且只能为其分配值c1
和{{1因为它们共享一个公共基类c2
。可以采取另一种方式:
Base
但它需要Base *b = new C1;
C1 *c1 = dynamic_cast <C1 *> (b);
并且需要一些名为RTTI(运行时类型信息)的东西,它为编译代码提供了一种方法来检查dynamic_cast
实际上是否指向b
类型。如果您要执行以下操作:
C1
Base *b = new C2;
C1 *c1 = dynamic_cast <C1 *> (b);
将是空指针,而不是c1
。但是C1和C2必须仍然有一个共同的基类来实现它。这不合法:
b
如果C1和C2相关(例如,CSquare和CCircle),那么公共基类是有意义的。如果它们不相关(例如,CRoad和CFood),那么一个共同的基类将无济于事(它可以完成,但它不是很合乎逻辑)。在其他答案中已经很好地描述了前者(公共基类)。如果您需要执行后者,那么您可能需要重新考虑代码的结构,以便您可以执行前者。
如果您可以扩展您想要对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
执行的操作,将会有所帮助。由于x
是一个容器,你只想做与容器相关的操作吗?
示例:
x