作为一般规则,我更喜欢在C++中使用值而不是指针语义(即使用vector<Class>而不是代替vector<Class*>).通常,由于不必记住删除动态分配的对象,因此性能的轻微损失可以弥补.
遗憾的是,当您想要存储所有派生自公共基础的对象类型时,值集合不起作用.请参阅下面的示例.
#include <iostream>
using namespace std;
class Parent
{
public:
Parent() : parent_mem(1) {}
virtual void write() { cout << "Parent: " << parent_mem << endl; }
int parent_mem;
};
class Child : public Parent
{
public:
Child() : child_mem(2) { parent_mem = 2; }
void write() { cout << "Child: " << parent_mem << ", " << child_mem << endl; }
int child_mem;
};
int main(int, char**)
{
// I can have a polymorphic container with pointer semantics
vector<Parent*> pointerVec;
pointerVec.push_back(new Parent());
pointerVec.push_back(new Child());
pointerVec[0]->write();
pointerVec[1]->write();
// Output:
//
// Parent: 1
// Child: 2, 2
// But I can't do it with value semantics
vector<Parent> valueVec;
valueVec.push_back(Parent());
valueVec.push_back(Child()); // gets turned into a Parent object :(
valueVec[0].write();
valueVec[1].write();
// Output:
//
// Parent: 1
// Parent: 2
}
Run Code Online (Sandbox Code Playgroud)
我的问题是:我可以拥有蛋糕(价值语义)并且吃它(多态容器)吗?或者我必须使用指针?
180*_*ION 24
由于不同类的对象具有不同的大小,如果将它们存储为值,最终会遇到切片问题.
一个合理的解决方案是存储容器安全智能指针.我通常使用boost :: shared_ptr,它可以安全地存储在容器中.请注意,std :: auto_ptr不是.
vector<shared_ptr<Parent>> vec;
vec.push_back(shared_ptr<Parent>(new Child()));
Run Code Online (Sandbox Code Playgroud)
shared_ptr使用引用计数,因此在删除所有引用之前,它不会删除基础实例.
ben*_*ben 10
是的你可以.
boost.ptr_container库提供标准容器的多态值语义版本.您只需要传入一个指向堆分配对象的指针,容器将获得所有权,所有进一步的操作将提供值语义,但回收所有权除外,它通过使用智能指针为您提供了几乎所有的值语义优势.
012*_*816 10
我只想指出vector <Foo>通常比vector <Foo*>更有效.在向量<Foo>中,所有Foo将在内存中彼此相邻.假设有一个冷TLB和缓存,第一次读取会将页面添加到TLB并将一大块向量拉入L#缓存; 后续读取将使用暖缓存和加载的TLB,偶尔会出现缓存未命中和TLB故障频率降低.
将其与向量<Foo*>进行对比:当您填充向量时,您从内存分配器获取Foo*.假设您的分配器不是非常智能,(tcmalloc?)或者随着时间的推移慢慢填充向量,每个Foo的位置可能与其他Foo相距很远:可能只有几百个字节,可能相差几兆字节.
在最坏的情况下,当你通过矢量扫描<美孚*>和非关联每个指针,你将收取一定的TLB错误和缓存未命中-这将最终会被很多比,如果你有一个vector <美孚>慢.(好吧,在最糟糕的情况下,每个Foo都被分页到磁盘,每次读取都会产生磁盘seek()和read()以将页面移回RAM.)
所以,在适当的时候继续使用vector <Foo>.:-)
您也可以考虑boost::any。我已经将它用于异构容器。读回值时,您需要执行 any_cast。如果失败,它将抛出 bad_any_cast。如果发生这种情况,您可以抓住并转到下一个类型。
我相信如果您尝试将派生类 any_cast 到其基类,它会抛出 bad_any_cast 。我尝试过这个:
Run Code Online (Sandbox Code Playgroud)// But you sort of can do it with boost::any. vector<any> valueVec; valueVec.push_back(any(Parent())); valueVec.push_back(any(Child())); // remains a Child, wrapped in an Any. Parent p = any_cast<Parent>(valueVec[0]); Child c = any_cast<Child>(valueVec[1]); p.write(); c.write(); // Output: // // Parent: 1 // Child: 2, 2 // Now try casting the child as a parent. try { Parent p2 = any_cast<Parent>(valueVec[1]); p2.write(); } catch (const boost::bad_any_cast &e) { cout << e.what() << endl; } // Output: // boost::bad_any_cast: failed conversion using boost::any_cast
话虽如此,我也会先走 shared_ptr 路线!只是觉得这可能会引起一些兴趣。
在寻找这个问题的答案时,我遇到了这个问题和一个类似的问题。在另一个问题的答案中,您会发现两个建议的解决方案:
包装器定义类所需的接口,并保存指向此类对象的指针。接口的实现是通过自由函数完成的。
以下是此模式的示例实现:
class Shape
{
public:
template<typename T>
Shape(T t)
: container(std::make_shared<Model<T>>(std::move(t)))
{}
friend void draw(const Shape &shape)
{
shape.container->drawImpl();
}
// add more functions similar to draw() here if you wish
// remember also to add a wrapper in the Concept and Model below
private:
struct Concept
{
virtual ~Concept() = default;
virtual void drawImpl() const = 0;
};
template<typename T>
struct Model : public Concept
{
Model(T x) : m_data(move(x)) { }
void drawImpl() const override
{
draw(m_data);
}
T m_data;
};
std::shared_ptr<const Concept> container;
};
Run Code Online (Sandbox Code Playgroud)
然后将不同的形状实现为常规结构/类。您可以自由选择是否要使用成员函数或自由函数(但您必须更新上述实现才能使用成员函数)。我更喜欢免费功能:
struct Circle
{
const double radius = 4.0;
};
struct Rectangle
{
const double width = 2.0;
const double height = 3.0;
};
void draw(const Circle &circle)
{
cout << "Drew circle with radius " << circle.radius << endl;
}
void draw(const Rectangle &rectangle)
{
cout << "Drew rectangle with width " << rectangle.width << endl;
}
Run Code Online (Sandbox Code Playgroud)
您现在可以将Circle和Rectangle对象添加到同一对象中std::vector<Shape>:
int main() {
std::vector<Shape> shapes;
shapes.emplace_back(Circle());
shapes.emplace_back(Rectangle());
for (const auto &shape : shapes) {
draw(shape);
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这种模式的缺点是它需要界面中的大量样板,因为每个函数需要定义三次。好处是你可以获得复制语义:
int main() {
Shape a = Circle();
Shape b = Rectangle();
b = a;
draw(a);
draw(b);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这会产生:
Drew rectangle with width 2
Drew rectangle with width 2
Run Code Online (Sandbox Code Playgroud)
如果您担心shared_ptr,可以将其替换为unique_ptr。但是,它将不再可复制,您必须移动所有对象或手动实施复制。Sean Parent 在他的演讲中详细讨论了这一点,上面提到的答案中显示了一个实现。
| 归档时间: |
|
| 查看次数: |
9540 次 |
| 最近记录: |