在C ++容器中作为模板参数提供的allocator和作为构造函数参数提供的allocator之间的区别?

时间:2016-06-14 00:26:15

标签: c++ vector stl containers allocator

提供STL容器(例如,std :: vector)与分配器作为模板参数之间的区别是什么,例如:

std::vector<int, std::allocator<int>> some_ints;

并提供一个分配器作为构造函数参数,例如:

std::allocator<int> temp;
std::vector<int> some_ints(temp);

并且两者的优点是什么,因为它们不是同一个东西(即一个提供类型,另一个是类型实例)并且可以彼此分开使用?

2 个答案:

答案 0 :(得分:7)

可以分开使用吗?

模板参数只提供类型。你还需要一个实例。这是不可分的。

就像拥有一个函数template<typename Type> f(Type instance);并询问Typeinstance之间的区别,它们可以单独使用吗?两者的优点是什么。如果你理解什么是模板,类型和实例/对象,那就没有多大意义了。

(为了简单起见,它是c ++ 11)

此处您有vector的类型模板:

template<
    class T,
    class Allocator = std::allocator<T>
> class vector;

这是默认的构造函数:

explicit vector( const Allocator& alloc = Allocator() );

始终有一个Allocator实例作为alloc参数提供。在这方面,所有其他调用都是类似的。默认情况下,它是默认构造的新Allocator对象。所以,从语义上讲,每当你不使用指向allocator参数的向量调用时,你就会创建新的Allocator对象(在默认情况下,它很可能什么都不做,但程序的逻辑流程如描述的那样) )。

你不能传递一些不适合Allocator的东西,因为你会遇到类型不匹配,或者在这种情况下恰好是替换失败。

如果不触及vector的定义,你可以做的一个非常非标准是定义DerivedAllocator派生自Allocator实例化它并作为参数传递。例如:

vector<T> v( DerivedAllocator<T>() );

但是我无法在脑海中提出这种构造的用例。有一个很好的用例,见下面的附录。

Allocator模板参数对于什么是有用的?

在某些系统中,您有多种类型的内存,因此提供单独的分配器(主要是单独的分配器类型)可能很有用。例如:SRamAllocatorRamAllocator

这在嵌入式系统中很常见。我知道那里有一个实现中的内存模型实际上并不是免费的,当你释放它时它就是一个丢失的块。它本质上是一个移动指针。理由是它非常快,因为它没有任何逻辑来跟踪由free引起的“漏洞”块。您不希望使用具有大量new / delete模式的方案。

allocator构造函数参数对于什么是有用的?

在有状态分配器的情况下有意义。想象一下,你想拥有两个相同类型的存储。例如。跟踪一些内存使用情况,或者你带来多少逻辑“内存库”的原因。您可能希望为程序中的每个线程创建一个分配器,因此更容易维护正确的CPU /内存亲和力。

当您创建新对象时,您需要告诉哪些分配器实例应该处理它。

你可以在技术上实现每个实例只使用不同类型的所有内容,但这会降低可能的运行时动态的可用性。

注意:默认分配器和pre-c ++ 11自定义分配器不允许具有状态,因此它们基本上以完全静态的方式实现。它实际上与您使用的Allocator实例无关。这就是默认Allocator()有效的原因。

因此,理论上人们不需要实例化它们,并且可以只使用类型和静态接口......如果标准这样说的话。但故意不允许这种方式允许具有内部状态的分配器类型(这句话是个人意见)

重要补充:我错过了c'tor参数分配器的一个重要特权,这很可能是 raison d'être多态分配器。此处详细介绍:polymorphic_allocator: when and why should I use it?

基本上,使用不同的Allocator类型会改变对象的整个类型,因此一端基本上是相同的对象,只有分配器才有区别。这在某些情况下是非常不受欢迎的。为了避免这种情况,可以编写多态分配器并在类型中使用基本分配器,并将具体实现用作运行时参数。 因此,使用不同的存储引擎可以拥有完全相同类型的对象。因此使用参数会产生一些开销,但它会将分配器的状态从铁品牌降低到类型,更多地实现细节。

答案 1 :(得分:1)

他们实际上 完全相同。

在第一个例子中,向量的默认构造函数default-构造一个你指定类型的分配器。

第二,你自己提供了分配器;它恰好匹配容器分配器的默认类型。

两个示例都使用默认参数;一个是默认函数参数,另一个是默认模板参数。但每种情况下的最终结果都是一样的。

这是一个示范性的例子:

// N.B. I've included one non-defaulted argument to match
// the vector example, but you could omit `T1` entirely and
// write e.g. `Foo<> obj4`.
template <typename T1, typename T2 = int>
struct Foo
{
   Foo(T2 x = 42) : x(x) {}

private:
   T2 x;
};

int main()
{
   Foo<char, int> obj1;      // like your first example
   Foo<char>      obj2(42);  // like your second example
   Foo<char>      obj3;      // this is more common

   // obj1, obj2 and obj3 are not only of identical type,
   // but also have identical state! You just got there
   // in different ways.
}