如何在一个列表中存储不同的数据类型?(C++)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3559412/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to store different data types in one list? (C++)
提问by hasdf
I need to store a list of various properties of an object. Property consists of a name and data, which can be of any datatype.
我需要存储一个对象的各种属性的列表。属性由名称和数据组成,可以是任何数据类型。
I know I can make a class "Property", and extend it with different PropertySubClasses which only differ with the datatype they are storing, but it does not feel right.
我知道我可以创建一个“Property”类,并使用不同的 PropertySubClass 扩展它,这些 PropertySubClasses 只与它们存储的数据类型不同,但感觉不对。
class Property
{
Property(std::string name);
virtual ~Property();
std::string m_name;
};
class PropertyBoolean : Property
{
PropertyBoolean(std::string name, bool data);
bool m_data;
};
class PropertyFloat : Property
{
PropertyFloat(std::string name, float data);
float m_data;
};
class PropertyVector : Property
{
PropertyVector(std::string name, std::vector<float> data);
std::vector<float> m_data;
};
Now I can store all kinds of properties in a
现在我可以将各种属性存储在一个
std::vector<Property*>
and to get the data, I can cast the object to the subclass. Or I can make a pure virtual function to do something with the data inside the function without the need of casting.
为了获取数据,我可以将对象转换为子类。或者我可以创建一个纯虚函数来处理函数内部的数据,而无需进行强制转换。
Anyways, this does not feel right to create these different kind of subclasses which only differ by the data type they are storing. Is there any other convenient way to achieve similar behavior?
无论如何,创建这些不同类型的子类只是它们存储的数据类型不同,这感觉不对。有没有其他方便的方法来实现类似的行为?
I do not have access to Boost.
我无权访问 Boost。
回答by sbi
C++ is a multi-paradigm language. It shines brightest and is most powerful where paradigms are mixed.
C++ 是一种多范式语言。在混合范式的地方,它最闪耀,最强大。
class Property
{
public:
Property(const std::string& name) //note: we don't lightly copy strings in C++
: m_name(name) {}
virtual ~Property() {}
private:
std::string m_name;
};
template< typename T >
class TypedProperty : public Property
{
public:
TypedProperty (const std::string& name, const T& data)
: Property(name), m_data(data);
private:
T m_data;
};
typedef std::vector< std::shared_ptr<Property> > property_list_type;
Edit:Why using std::shared_ptr<Property>
instead of Property*
?
Consider this code:
编辑:为什么使用std::shared_ptr<Property>
而不是Property*
?
考虑这个代码:
void f()
{
std::vector<Property*> my_property_list;
for(unsigned int u=0; u<10; ++u)
my_property_list.push_back(new Property(u));
use_property_list(my_property_list);
for(std::vector<Property*>::iterator it=my_property_list.begin();
it!=my_property_list.end(); ++it)
delete *it;
}
That for
loop there attempts to cleanup, deleting all the properties in the vector, just before it goes out of scope and takes all the pointers with it.
Now, while this might seem fine for a novice, if you're an only mildly experienced C++ developer, that code should raise alarm bells as soon as you look at it.
该for
循环尝试清理,删除向量中的所有属性,就在它超出范围并带走所有指针之前。
现在,虽然这对于新手来说似乎没问题,但如果您只是一位经验丰富的 C++ 开发人员,那么一旦您看到该代码,就会引起警钟。
The problem is that the call to use_property_list()
might throw an exception. If so, the function f()
will be left right away. In order to properly cleanup, the destructors for all automatic objects created in f()
will be called. That is, my_property_list
will be properly destroyed. std::vector
's destructor will then nicely cleanup the data it holds. However, it holds pointers, and how should std::vector
know whether these pointers are the last ones referencing their objects?
Since it doesn't know, it won't delete the objects, it will only destroy the pointers when it destroys its content, leaving you with objects on the heap that you don't have any pointers to anymore. This is what's called a "leak".
问题是对 的调用use_property_list()
可能会引发异常。如果是这样,该功能f()
将立即离开。为了正确清理,f()
将调用所有在其中创建的自动对象的析构函数。也就是说,my_property_list
将被适当地销毁。std::vector
的析构函数然后会很好地清理它持有的数据。但是,它持有指针,如何std::vector
知道这些指针是否是最后一个引用其对象的指针?
由于它不知道,它不会删除对象,它只会在销毁其内容时销毁指针,从而在堆上留下您不再有任何指针指向的对象。这就是所谓的“泄漏”。
In order to avoid that, you would need to catch all exceptions, clean up the properties, and the rethrow the exception. But then, ten years from now, someone has to add a new feature to the 10MLoC application this has grown to, and, being in a hurry, adds code which leaves that function prematurely when some condition holds. The code is tested and it works and doesn't crash - only the server it's part of now leaks a few bytes an hour, making it crash due to being out of memory about once a week. Finding that makes for many hours of finedebugging.
为了避免这种情况,您需要捕获所有异常,清理属性,然后重新抛出异常。但是,十年后,必须有人为 10MLoC 应用程序添加一个新功能,这已经发展到了,并且匆忙添加了在某些条件成立时过早离开该功能的代码。代码已经过测试,它可以工作并且不会崩溃 - 只有它所在的服务器现在每小时泄漏几个字节,导致它由于大约每周一次内存不足而崩溃。发现这可以进行数小时的精细调试。
Bottom line: Never manage resources manually, always wrap them in objects of a class designed to handle exactly one instance of such a resource. For dynamically allocated objects, those handles are called "smart pointer", and the most used one is shared_ptr
.
底线:永远不要手动管理资源,始终将它们包装在旨在处理此类资源的一个实例的类的对象中。对于动态分配的对象,这些句柄称为“智能指针”,最常用的是shared_ptr
.
回答by Grumdrig
A lower-level way is to use a union
一种较低级别的方法是使用联合
class Property
union {
int int_data;
bool bool_data;
std::cstring* string_data;
};
enum { INT_PROP, BOOL_PROP, STRING_PROP } data_type;
// ... more smarts ...
};
Dunno why your other solution doesn't feel right, so I don't know if this way would feel better to you.
不知道为什么您的其他解决方案感觉不对,所以我不知道这种方式是否会让您感觉更好。
EDIT: Some more code to give an example of usage.
编辑:一些更多的代码来举例说明用法。
Property car = collection_of_properties.head();
if (car.data_type == Property::INT_PROP) {
printf("The integer property is %d\n", car.int_data);
} // etc.
I'd probably put that sort of logic into a method of the class where possible. You'd also have members such as this constructor to keep the data and type field in sync:
我可能会在可能的情况下将这种逻辑放入类的方法中。您还将拥有诸如此构造函数之类的成员来保持数据和类型字段同步:
Property::Property(bool value) {
bool_data = value;
data_type = BOOL_PROP;
}
回答by fredoverflow
I suggest boost::variant
or boost::any
. [Related question]
我建议boost::variant
或boost::any
。[相关问题]
回答by fredoverflow
Write a template class Property<T>
that derives from Property
with a data member of type T
编写一个模板类Property<T>
,该类派生自Property
具有类型的数据成员T
回答by Johann Gerell
I see that there are lots of shots at trying to solve your problem by now, but I have a feeling that you're looking in the wrong end - why do you actuallywant to do this in the first place? Is there some interesting functionality in the base class that you have omitted to specify?
我看到有很多镜头在试图解决现在的问题,但我有一种感觉,你找错到底-你为什么竟想这样做摆在首位?基类中是否有一些有趣的功能您没有指定?
The fact that you'd be forced to switch on a property type id to do what you want with a specific instance is a code smell, especiallywhen the subclasses have absolutely nothing in common via the base class other than a name (which is the type id in this case).
您被迫打开属性类型 id 以对特定实例执行您想要的操作这一事实是一种代码异味,尤其是当子类通过基类除了名称(这是在这种情况下输入 id)。
回答by phlipsy
Another possible solution is to write a intermediate class managing the pointers to Property
classes:
另一种可能的解决方案是编写一个管理Property
类指针的中间类:
class Bla {
private:
Property* mp
public:
explicit Bla(Property* p) : mp(p) { }
~Bla() { delete p; }
// The standard copy constructor
// and assignment operator
// aren't sufficient in this case:
// They would only copy the
// pointer mp (shallow copy)
Bla(const Bla* b) : mp(b.mp->clone()) { }
Bla& operator = (Bla b) { // copy'n'swap trick
swap(b);
return *this;
}
void swap(Bla& b) {
using std::swap; // #include <algorithm>
swap(mp, b.mp);
}
Property* operator -> () const {
return mp;
}
Property& operator * () const {
return *mp;
}
};
You have to add a virtual clone
method to your classes returning a pointer to a newly created copy of itself:
你必须向clone
你的类添加一个虚拟方法,返回一个指向它自己新创建的副本的指针:
class StringProperty : public Property {
// ...
public:
// ...
virtual Property* clone() { return new StringProperty(*this); }
// ...
};
Then you'll be able to do this:
然后你将能够做到这一点:
std::vector<Bla> v;
v.push_back(Bla(new StringProperty("Name", "Jon Doe")));
// ...
std::vector<Bla>::const_iterator i = v.begin();
(*i)->some_virtual_method();
Leaving the scope of v
means that all Bla
s will be destroyed freeing automatically the pointers they're holding. Due to its overloaded dereferencing and indirection operator the class Bla
behaves like an ordinary pointer. In the last line *i
returns a reference to a Bla
object and using ->
means the same as if it was a pointer to a Property
object.
离开范围v
意味着所有Bla
s 都将被销毁并自动释放它们持有的指针。由于其重载的解引用和间接操作符,该类的Bla
行为就像一个普通的指针。在最后一行*i
返回对Bla
对象的引用,使用的->
方式与指向Property
对象的指针相同。
A possible drawback of this approach is that you always get a heap operation (a new
and a delete
) if the intermediate objects must be copied around. This happens for example if you exceed the vector's capacity and all intermediate objects must be copied to a new piece of memory.
这种方法的一个可能的缺点是,如果必须复制中间对象,您总是会得到一个堆操作(anew
和 a delete
)。例如,如果超出向量的容量并且所有中间对象必须复制到新的内存中,就会发生这种情况。
In the new standard (i.e. c++0x) you'll be able to use the unique_ptr
template: It
在新标准(即 c++0x)中,您将能够使用unique_ptr
模板:它
- can be used inside the standard containers (in contrast to the
auto_ptr
which must not be used in the standard containers), - offers the usually faster move semantics (it can easily passed around) and
- takes care over the held pointers (it frees them automatically).
- 可以在标准容器内使用(与
auto_ptr
不能在标准容器中使用的相反), - 提供通常更快的移动语义(它可以很容易地传递)和
- 照顾持有的指针(它会自动释放它们)。
回答by Dima
You can probably do this with the Boost library, or you could create a class with a type code and a void
pointer to the data, but it would mean giving up some of the type safety of C++. In other words, if you have a property "foo", whose value is an integer, and give it a string value instead, the compiler will not find the error for you.
您可能可以使用 Boost 库来做到这一点,或者您可以创建一个带有类型代码和void
指向数据的指针的类,但这意味着放弃 C++ 的某些类型安全性。换句话说,如果你有一个属性“foo”,它的值是一个整数,而是给它一个字符串值,编译器不会为你找到错误。
I would recommend revisiting your design, and re-evaluating whether or not you really need so much flexibility. Do you really need to be able to handle properties of any type? If you can narrow it down to just a few types, you may be able to come up with a solution using inheritance or templates, without having to "fight the language".
我建议重新审视您的设计,并重新评估您是否真的需要如此大的灵活性。你真的需要能够处理任何类型的属性吗?如果您可以将其缩小到几种类型,您也许可以使用继承或模板提出解决方案,而不必“与语言作斗争”。