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

Difference between allocator supplied as template parameter and allocator supplied as constructor argument in C++ containers?

将分配器作为模板参数提供给 STL 容器(例如 std::vector)有什么区别,例如:

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

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

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

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

可以分开使用吗?

模板参数只提供类型。您仍然需要一个实例。不可分离。

就像有个函数template<typename Type> f(Type instance);,问Typeinstance有什么区别,能不能分开用,各自有什么优点。如果您确实了解什么是模板、类型和 instance/object.

,那就没有多大意义了

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

这里有 vector 的类型模板:

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

这是默认构造函数:

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

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

你不能传递不适合的东西 Allocator 因为你会得到类型不匹配,或者在这种情况下准确地说是替换失败。

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

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

但我无法在脑海中想出这种构造的用例。有一个很好的用例,请参阅下面的附录.

Allocator 模板参数有什么用?

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

这在嵌入式系统中很常见。我知道在某个地方有一个内存模型在实现中实际上并没有释放,当你释放它时它是一个丢失的块。它本质上是一个移动指针。理由是它非常快,因为它没有任何逻辑来跟踪由 freeing 引起的 "holes" 块。您不想在具有大量 new/delete 模式的场景中使用它。

allocator 构造函数参数有什么用?

在有状态分配器的情况下是有意义的。想象一下,您想要两个相同类型的存储。例如。跟踪一些内存使用情况,或者无论出于何种原因您拥有多个逻辑 "memory banks"。您可能希望为程序中的每个线程创建一个分配器,这样更容易保持正确的 CPU/memory 亲和力。

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

从技术上讲,您可以为每个实例使用不同的类型来实现所有内容,但这会降低可能的 运行 时间动态性的可用性。

注意:默认分配器和 c++11 之前的自定义分配器不允许有状态,因此它们基本上以完全静态的方式实现。您使用的 Allocator 实例实际上并不重要。这就是默认 Allocator() 起作用的原因。

因此,从理论上讲,人们不需要实例化它们,并且可以只使用类型和静态接口工作......如果标准是这样的话。但是故意不这样做是为了让allocator types with an internal state(这句话是个人意见).

重要附录:我错过了 c'tor 参数分配器的一个重要优点,这很可能是 raison d'être. 多态分配器。 在这里有详细描述:

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

它们实际上完全一样的东西。

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

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

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

这是一个示范性的例子:

// 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.
}