我需要存储一个对象的各种属性的列表。属性由名称和数据组成,可以是任何数据类型。
我知道我可以创建一个类“Property”,并使用不同的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;
};
现在我可以在
中存储各种属性 std::vector<Property*>
为了获取数据,我可以将对象强制转换为子类。或者我可以创建一个纯虚函数来对函数内部的数据执行某些操作,而无需进行转换。
无论如何,创建这些不同类型的子类是不对的,这些子类只是因为它们存储的数据类型而不同。有没有其他方便的方法来实现类似的行为?
我无法访问Boost。
答案 0 :(得分:26)
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;
修改: 为什么使用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;
}
那个for
循环试图清除,删除向量中的所有属性,就在它超出范围之前并带走所有指针。
现在,虽然这对初学者来说似乎没什么问题,但如果你是一个经验丰富的C ++开发人员,那么一旦你看到它,代码应该会引起警钟。
问题是对use_property_list()
的调用可能会引发异常。如果是这样,函数f()
将立即被删除。为了正确清理,将调用f()
中创建的所有自动对象的析构函数。也就是说,my_property_list
将被正确销毁。然后std::vector
的析构函数将很好地清理它所拥有的数据。 但是,它包含指针 ,std::vector
应该如何知道这些指针是否是引用其对象的最后一个指针?
由于它不知道,它不会删除对象,它只会在销毁内容时销毁指针,让你在堆上找不到任何指针的对象。这就是所谓的“泄漏”。
为了避免这种情况,您需要捕获所有异常,清理属性以及重新抛出异常。但是,从现在起十年后,有人必须为10MLoC应用程序添加一个新功能,这个功能已经发展到了,并且匆忙地添加了代码,当某些条件成立时,代码会过早地保留该功能。代码经过测试,它可以运行并且不会崩溃 - 只有它现在所属的服务器每小时泄漏几个字节,因为每周一次内存不足而导致崩溃。查找可以进行数小时的精细调试。
底线:永远不要手动管理资源,始终将它们包装在专门处理此类资源的一个实例的类的对象中。对于动态分配的对象,这些句柄称为“智能指针”,最常用的句柄是shared_ptr
。
答案 1 :(得分:9)
较低级别的方法是使用联合
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为什么你的其他解决方案感觉不对,所以我不知道这种方式对你来说是不是更好。
编辑:提供一些使用示例的代码。
Property car = collection_of_properties.head();
if (car.data_type == Property::INT_PROP) {
printf("The integer property is %d\n", car.int_data);
} // etc.
我可能会在可能的情况下将这种逻辑放入类的方法中。您还可以使用此构造函数之类的成员来保持数据和类型字段同步:
Property::Property(bool value) {
bool_data = value;
data_type = BOOL_PROP;
}
答案 2 :(得分:4)
我建议boost::variant
或boost::any
。 [Related question]
答案 3 :(得分:2)
编写一个模板类Property<T>
,该类来自Property
,其数据成员类型为T
答案 4 :(得分:1)
另一种可能的解决方案是编写一个管理指向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;
}
};
您必须向类添加一个虚拟clone
方法,返回指向新创建的自身副本的指针:
class StringProperty : public Property {
// ...
public:
// ...
virtual Property* clone() { return new StringProperty(*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();
保留v
的范围意味着将销毁所有Bla
s自动释放他们持有的指针。由于其重载引用和间接运算符,类Bla
的行为类似于普通指针。在最后一行*i
返回对Bla
对象的引用,使用->
表示与指向Property
对象的指针相同。
这种方法的一个可能的缺点是,如果必须复制中间对象,则始终获得堆操作(new
和delete
)。例如,如果超出向量的容量,则必须将所有中间对象复制到新的内存中。
在新标准(即c ++ 0x)中,您将能够使用unique_ptr
模板:它
auto_ptr
不同,不能在标准容器中使用),答案 5 :(得分:1)
我看到现在有很多尝试解决你的问题,但我觉得你看错了 - 为什么你实际想要这样做首先?基类中是否有一些有趣的功能,你省略了指定?
你被迫打开一个属性类型id来做你想要的特定实例的事实是代码气味,尤其是当子类完全没有任何共同点时通过除了名称之外的基类(在这种情况下是类型id)。
答案 6 :(得分:0)
您可以使用Boost库执行此操作,或者您可以创建一个类型代码和指向数据的void
指针的类,但这意味着放弃一些C ++的类型安全性。换句话说,如果你有一个属性“foo”,其值是一个整数,并给它一个字符串值,编译器将不会为你找到错误。
我建议您重新审视您的设计,并重新评估您是否真的需要这么大的灵活性。你真的需要能够处理任何类型的属性吗?如果您可以将其缩小到几种类型,您可以使用继承或模板来提出解决方案,而无需“对抗语言”。