基本上,这与
boost::any
非常接近,有许多区别。从我的角度来看,它可能是boost::any
的完全替代品,我想将其称为some
。细微差别:
成员函数,引用(不是指针) ),基于类型检查/未检查的数据访问(广播)。
通过
dynamic_cast
而不是手动typeid
检查简化了类型检查。使用std::bad_cast
而不是自定义异常类型。没有引用被删除。转换运算符。
没有
decay
的黑客攻击。没有
const_cast
的功能公开; type()
检查界面代替。在内部,自定义类型识别机制绕过is<T>()
和RTTI。完全支持移动语义。没有限制例如在读取
typeid
右值引用或为临时对象提供非const
右值引用时。使用空基本优化,空对象存储在所需虚拟函数表的顶部,而没有空间开销。
主要区别:
以内存管理对象为模板,大致类似于STL容器的分配器,但具有不同的自定义接口。
默认的“分配器”类型在堆栈上提供可自定义的存储空间。与小型字符串优化类似,并且没有运行时开销,不超过此空间的对象放在堆栈中;免费商店中较大的商品。
因此,
const
可能在some
和boost::any
之间(使用堆栈而不是限制为预定义的类型)。要大致了解此“分配器”,以下是仅使用免费存储的情况:struct alloc
{
template<typename D, typename V>
D *copy(V &&v) { return new D{std::forward<V>(v)}; }
template<typename D, typename V, typename B>
B *move(V &&v, B *&p) { B *q = p; p = nullptr; return q; }
template<typename D>
void free(D *p) { delete p; }
};
确实可以,但是在我的示例中未使用。这是实际的默认值,包括它自己的堆栈空间:
template<size_t N = 16>
class store
{
char space[N];
template<typename T>
static constexpr bool
fits() { return sizeof(typename std::decay<T>::type) <= N; }
public:
template<typename D, typename V>
D *copy(V &&v)
{
return fits<D>() ? new(space) D{std::forward<V>(v)} :
new D{std::forward<V>(v)};
}
template<typename D, typename V, typename B>
B *move(V &&v, B *&p)
{
B *q = fits<D>() ? copy<D>(std::forward<V>(v)) : p;
p = nullptr;
return q;
}
template<typename D>
void free(D *p) { fits<D>() ? p->~D() : delete p; }
};
通常,在64位计算机上,8个字节用于虚函数表,因此默认大小的其余8个可以存储例如
boost::variant
或long
而不调用任何免费存储操作。表达式
double
在编译时求值,因此,每当其求值为false时,fits<D>()
就等同于store
,而没有任何运行时开销。对于alloc
,它始终是等效的(或者我认为是吧?)。现在,这是
N = 0
的完整定义:template<typename A = store<>>
class some : A
{
using id = size_t;
template<typename T>
struct type { static void id() { } };
template<typename T>
static id type_id() { return reinterpret_cast<id>(&type<T>::id); }
template<typename T>
using decay = typename std::decay<T>::type;
template<typename T>
using none = typename std::enable_if<!std::is_same<some, T>::value>::type;
//-----------------------------------------------------------------------------
struct base
{
virtual ~base() { }
virtual bool is(id) const = 0;
virtual base *copy(A&) const = 0;
virtual base *move(A&, base*&) = 0;
virtual void free(A&) = 0;
} *p = nullptr;
template<typename T>
struct data : base, std::tuple<T>
{
using std::tuple<T>::tuple;
T &get() & { return std::get<0>(*this); }
T const &get() const& { return std::get<0>(*this); }
bool is(id i) const override { return i == type_id<T>(); }
base *copy(A &a) const override
{
return a.template copy<data>(get());
}
base *move(A &a, base *&p) override
{
return a.template move<data>(std::move(get()), p);
}
void free(A &a) override { a.free(this); }
};
//-----------------------------------------------------------------------------
template<typename T>
T &stat() { return static_cast<data<T>*>(p)->get(); }
template<typename T>
T const &stat() const { return static_cast<data<T> const*>(p)->get(); }
template<typename T>
T &dyn() { return dynamic_cast<data<T>&>(*p).get(); }
template<typename T>
T const &dyn() const { return dynamic_cast<data<T> const&>(*p).get(); }
base *move(some &s) { return s.p->move(*this, s.p); }
base *copy(some const &s) { return s.p->copy(*this); }
base *read(some &&s) { return s.p ? move(s) : s.p; }
base *read(some const &s) { return s.p ? copy(s) : s.p; }
template<typename V, typename U = decay<V>, typename = none<U>>
base *read(V &&v) { return A::template copy<data<U>>(std::forward<V>(v)); }
template<typename X>
some &assign(X &&x)
{
if (!p) p = read(std::forward<X>(x));
else
{
some t{std::move(*this)};
try { p = read(std::forward<X>(x)); }
catch(...) { p = move(t); throw; }
}
return *this;
}
void swap(some &s)
{
if (!p) p = read(std::move(s));
else if (!s.p) s.p = move(*this);
else
{
some t{std::move(*this)};
try { p = move(s); }
catch(...) { p = move(t); throw; }
s.p = move(t);
}
}
//-----------------------------------------------------------------------------
public:
some() { }
~some() { if (p) p->free(*this); }
some(some &&s) : p{read(std::move(s))} { }
some(some const &s) : p{read(s)} { }
template<typename V, typename = none<decay<V>>>
some(V &&v) : p{read(std::forward<V>(v))} { }
some &operator=(some &&s) { return assign(std::move(s)); }
some &operator=(some const &s) { return assign(s); }
template<typename V, typename = none<decay<V>>>
some &operator=(V &&v) { return assign(std::forward<V>(v)); }
friend void swap(some &s, some &r) { s.swap(r); }
void clear() { if(p) { p->free(*this); p = nullptr; } }
bool empty() const { return p; }
template<typename T>
bool is() const { return p ? p->is(type_id<T>()) : false; }
template<typename T> T &&_() && { return std::move(stat<T>()); }
template<typename T> T &_() & { return stat<T>(); }
template<typename T> T const &_() const& { return stat<T>(); }
template<typename T> T &&cast() && { return std::move(dyn<T>()); }
template<typename T> T &cast() & { return dyn<T>(); }
template<typename T> T const &cast() const& { return dyn<T>(); }
template<typename T> operator T &&() && { return std::move(_<T>()); }
template<typename T> operator T &() & { return _<T>(); }
template<typename T> operator T const&() const& { return _<T>(); }
};
这里是一个实时示例,其中包含针对大多数功能的广泛测试。
现在,我想要的是:
函数/变量名称,缩进等。我知道代码并不是真正用于共享;它非常紧凑,尽可能使用单行函数定义,在大多数情况下使用单字母参数,并且没有注释。请忽略它。
您能看到或发现任何可能绕过我的测试的错误吗?有泄漏的可能吗?我知道这一定是低级代码。
您是否看到任何不必要的性能损失或任何其他可以轻松进一步优化的内容?
我已尽力提供了强有力的异常保证所有操作。
some
和assign
是最简单的代码,由于(潜在的)使用堆栈,操作顺序不够,我不得不在最关键的操作周围编写swap
/ try
块。您认为一切正确吗?例如,我假设如果将
catch
移至A
而没有B
,那么throw
可以安全地移回B
。如果是,是否可以通过更高效的方式来完成?例如,在
A
中,我正在进行其他移动(实际上是复制)操作,以处理某些构造函数引发的可能性。我发现所有测试都可以正常工作,而无需作业自测。
我倾向于显式地使用构造函数(placement-
assign
)和析构函数进行堆栈操作。我知道直接使用new
等同于琐碎可复制的类型,并且效率更高,但是我认为它太底层了,会使代码更加复杂。我应该重新考虑吗?它是对
std::copy
的对象定义良好还是通过对delete
函数的调用显式调用其析构函数(即使可以确定这是对它的最后一个操作)?我找不到其他选择正确操作的方法(virtual
与析构函数调用)。不确定是否需要,但是,任何想法如何简化分配器接口,以便可以在更通用的接口中使用设置?我知道在任何情况下它都与
delete
明显不同。#1 楼
some
内部存储的想法使我想到了swap()
和assign()
带来的问题。我首先想到的是将swap()
添加为另一种虚拟方法,甚至可能是分配。这可以帮助您以另一种不同的方式解决问题-您将知道是要移动指针还是需要将对象从一个嵌入式存储复制/移动到另一个嵌入式存储。但是,三思而后行,使用一些自由列表和自旋锁或其他一些无锁技术,也可以使用良好的分配器为小型对象解决幕后的优化问题。 boost :: pool或我自己的相同大小的分配器(非常老的项目)。这将极大地帮助您解决交换/分配中的异常问题,而无需临时进行
try..catch
和修复。如果您仍然考虑使用存储,那么我不会在
some
中继承它,但在包含指针的内部类(一个具有指针的基类)和存储的下一个(第二基类或组成,对于下一段而言会更好)中。通过使其在读取时更具顺序性(可以先指向指针,然后指向vmtptr,最后指向数据),可以进一步提高速度。您也可以将vmtptr移到数据指针旁边(无论存储如何),并通过一些新的放置技巧(将虚拟方法与数据分离,以及一些可怕的技巧来访问this
旁边的数据指针)获得更多提示)。我不会写这样的内容,因为您不会试图通过嵌入式存储对其进行优化,而嵌入式存储本身就显得过分了-std::allocator
应该可以解决这个问题-我仍然认为解决此问题(快速分配微小对象)将使您的嵌入式存储不再需要(并可能会帮助其他容器)。分配器似乎更像是
some
的功能,而不是分配器或可用的扩展/选项。我个人不会公开它并使some
成为一个简单的类(而不是模板)。或传递一个真实的(但通用的)分配器,例如std::allocator<char>
,其中allocate
和deallocate
针对微小块进行了优化。您还可以对所有类型使用rebind
。Alloc / Store接口
copy
接受通用(右值) reference和thefore看起来像是其他东西,但是从代码中我可以看到您仅在一个地方使用它-data::copy
,它与简单的(lvalue)参考一起使用。我认为更改签名以匹配它是一个好主意(否则它可能会成为一种移动而不是复制)。实际上,它模仿construct(allocate(1), v)
。似乎可以将数据从一个存储移动到另一个存储(嵌入式或堆)。它看起来像是在为它设计的目的,但我将内部move
更改为copy<D>
,以使其更加清晰(并且易于理解)。new(space) ...
很好,这意味着可以销毁+可以进行处置。 free
是我肯定会添加的东西(使用std :: swap)来帮助您解决swap
中存在的问题-我只是从中复制some::swap()
并删除noexcept
。它根本不应该抛出,并且可以利用std :: swap和可能的专业化优势(try..catch
-您可能会在这里寻找复制和交换以获取更多相关信息)。作者:表达式
using std::swap; swap(x,y)
是在编译时求值的,因此,每当求值为false时,fits<D>()
就等于store
,而没有任何运行时开销。对于alloc
,它始终是等效的(或者我认为是吧?)。对我来说,它似乎与此相关:
template<size_t N = 16>
class store
{
char space[N];
template<typename T>
static constexpr bool
fits() { return sizeof(typename std::decay<T>::type) <= N; }
N = 0
永远不会返回负值,根据我所知道的,结构末尾的零大小数组是允许的(至少我已经使用过几次很好的有效扩展名)。如果遇到问题,您可以简单地对模板进行专门化(对于sizeof
)。现在看来我没有什么可添加的,除非看到一些反馈;)
评论
经过t {std :: move(* this)};之后,UB是否可以访问该类中的任何内容?@BЈовић之后,我知道p == nullptr且先前存储在空间中的任何内容均已移动,因此空间可以自由重用(其内容未定义,但我不尝试读取它)。这正是默认初始化后的状态,即*此为空。接下来的操作的形式为p = ...,即* this中有新内容。我认为这是明确定义的,不是吗?
不知道。您正在做回报* this;之后。
如何使用id = void(*)();您永远不知道函数指针的大小。还要将您的任何文件放入某些存储库中。
@ user1095108你是对的,这是一个弱点。这就是为什么我后来为类型ID编写了一种更简洁的解决方案的原因,该类型根本不需要任何reinterpret_cast。经过改进的任何部件(称为某些部件)现在都在这里。它已经是存储库的一部分,但不再独立。除其他外,它使用改进的类型ID(属于同一存储库的一部分)。