STL/CLR 容器
STL/CLR 库由类似于 C++ 标准库中的容器组成,但它在 .NET Framework 的托管环境中运行。 它为支持旧版而予以维护,但并未与实际的 C++ 标准库保持同步。
本文档提供 STL/CLR 中的容器的概述,例如容器元素的需求、可以插入到容器中的元素类型以及容器中的元素的所有权问题。 在适当的地方,将会提及本机 C++ 标准库与 STL/CLR 之间的差异。
容器元素的需求
插入到 STL/CLR 容器中的所有元素必须遵循某些准则。 有关详细信息,请参阅 STL/CLR 容器元素的要求。
有效的容器元素
STL/CLR 容器可容纳以下两种元素类型之一:
指向引用类型的句柄。
引用类型。
未装箱的值类型。
您不能将装箱的值类型插入到任何 STL/CLR 容器中。
指向引用类型的句柄
您可以将指向引用类型的句柄插入到 STL/CLR 容器中。 C++ 中面向 CLR 的句柄与 C++ 中的指针类似。 有关详细信息,请参阅 对象运算符 (^) 的句柄。
示例
以下示例演示了如何将指向 Employee 对象的句柄插入到 cliext::set 中。
// cliext_container_valid_reference_handle.cpp
// compile with: /clr
#include <cliext/set>
using namespace cliext;
using namespace System;
ref class Employee
{
public:
// STL/CLR containers might require a public constructor, so it
// is a good idea to define one.
Employee() :
name(nullptr),
employeeNumber(0) { }
// All STL/CLR containers require a public copy constructor.
Employee(const Employee% orig) :
name(orig.name),
employeeNumber(orig.employeeNumber) { }
// All STL/CLR containers require a public assignment operator.
Employee% operator=(const Employee% orig)
{
if (this != %orig)
{
name = orig.name;
employeeNumber = orig.employeeNumber;
}
return *this;
}
// All STL/CLR containers require a public destructor.
~Employee() { }
// Associative containers such as maps and sets
// require a comparison operator to be defined
// to determine proper ordering.
bool operator<(const Employee^ rhs)
{
return (employeeNumber < rhs->employeeNumber);
}
// The employee's name.
property String^ Name
{
String^ get() { return name; }
void set(String^ value) { name = value; }
}
// The employee's employee number.
property int EmployeeNumber
{
int get() { return employeeNumber; }
void set(int value) { employeeNumber = value; }
}
private:
String^ name;
int employeeNumber;
};
int main()
{
// Create a new employee object.
Employee^ empl1419 = gcnew Employee();
empl1419->Name = L"Darin Lockert";
empl1419->EmployeeNumber = 1419;
// Add the employee to the set of all employees.
set<Employee^>^ emplSet = gcnew set<Employee^>();
emplSet->insert(empl1419);
// List all employees of the company.
for each (Employee^ empl in emplSet)
{
Console::WriteLine("Employee Number {0}: {1}",
empl->EmployeeNumber, empl->Name);
}
return 0;
}
引用类型
还可以将引用类型(而不是指向引用类型的句柄)插入到 STL/CLR 容器中。 此处的主要差异是,当删除类型引用的容器时,将为容器中的所有元素调用析构函数。 在指向引用类型的句柄的容器中,则不会调用这些元素的析构函数。
示例
以下示例演示了如何将 Employee 对象插入到 cliext::set
中。
// cliext_container_valid_reference.cpp
// compile with: /clr
#include <cliext/set>
using namespace cliext;
using namespace System;
ref class Employee
{
public:
// STL/CLR containers might require a public constructor, so it
// is a good idea to define one.
Employee() :
name(nullptr),
employeeNumber(0) { }
// All STL/CLR containers require a public copy constructor.
Employee(const Employee% orig) :
name(orig.name),
employeeNumber(orig.employeeNumber) { }
// All STL/CLR containers require a public assignment operator.
Employee% operator=(const Employee% orig)
{
if (this != %orig)
{
name = orig.name;
employeeNumber = orig.employeeNumber;
}
return *this;
}
// All STL/CLR containers require a public destructor.
~Employee() { }
// Associative containers such as maps and sets
// require a comparison operator to be defined
// to determine proper ordering.
bool operator<(const Employee^ rhs)
{
return (employeeNumber < rhs->employeeNumber);
}
// The employee's name.
property String^ Name
{
String^ get() { return name; }
void set(String^ value) { name = value; }
}
// The employee's employee number.
property int EmployeeNumber
{
int get() { return employeeNumber; }
void set(int value) { employeeNumber = value; }
}
private:
String^ name;
int employeeNumber;
};
int main()
{
// Create a new employee object.
Employee empl1419;
empl1419.Name = L"Darin Lockert";
empl1419.EmployeeNumber = 1419;
// Add the employee to the set of all employees.
set<Employee>^ emplSet = gcnew set<Employee>();
emplSet->insert(empl1419);
// List all employees of the company.
for each (Employee^ empl in emplSet)
{
Console::WriteLine("Employee Number {0}: {1}",
empl->EmployeeNumber, empl->Name);
}
return 0;
}
未装箱的值类型
您还可以将未装箱的值类型插入到 STL/CLR 容器中。 未装箱的值类型是尚未装箱到引用类型中的值类型。
值类型元素可以是标准值类型之一,如 int
,也可以是用户定义的值类型,如 value class
。 有关详细信息,请参阅类和结构
示例
以下示例对第一个示例进行了修改,使 Employee 类成为一个值类型。 然后,将此值类型插入到 cliext::set
中,与在第一个示例中一样。
// cliext_container_valid_valuetype.cpp
// compile with: /clr
#include <cliext/set>
using namespace cliext;
using namespace System;
value class Employee
{
public:
// Associative containers such as maps and sets
// require a comparison operator to be defined
// to determine proper ordering.
bool operator<(const Employee^ rhs)
{
return (employeeNumber < rhs->employeeNumber);
}
// The employee's name.
property String^ Name
{
String^ get() { return name; }
void set(String^ value) { name = value; }
}
// The employee's employee number.
property int EmployeeNumber
{
int get() { return employeeNumber; }
void set(int value) { employeeNumber = value; }
}
private:
String^ name;
int employeeNumber;
};
int main()
{
// Create a new employee object.
Employee empl1419;
empl1419.Name = L"Darin Lockert";
empl1419.EmployeeNumber = 1419;
// Add the employee to the set of all employees.
set<Employee>^ emplSet = gcnew set<Employee>();
emplSet->insert(empl1419);
// List all employees of the company.
for each (Employee empl in emplSet)
{
Console::WriteLine("Employee Number {0}: {1}",
empl.EmployeeNumber, empl.Name);
}
return 0;
}
如果尝试将指向值类型的句柄插入到容器中,则会生成编译器错误 C3225。
性能和内存含义
在确定是否使用句柄来引用类型或值类型作为容器元素时,您必须考虑多种因素。 如果您决定使用值类型,请记住,每次将一个元素插入到容器中时,都会生成该元素的副本。 对于小对象,这应该不是问题,但是,如果插入的对象很大,则可能大大影响性能。 此外,如果使用的是值类型,则无法将一个元素同时存储在多个容器中,因为每个容器都有自己的元素副本。
如果您决定使用句柄来引用类型,则可能会改善性能,因为在容器中插入元素时无需生成元素的副本。 此外,与值类型不同的是,同一元素可以存在于多个容器中。 但是,其中如果您决定使用句柄,则必须注意确保句柄有效,且未在程序的其他地方删除它所引用的对象。
容器的所有权问题
STL/CLR 中的容器处理值语义。 每次将一个元素插入到容器中时,都会插入该元素的副本。 如果要获取类似引用的语义,则可插入指向对象的句柄,而不是对象本身。
当您调用句柄对象容器的清除或擦除方法时,不会从内存中释放句柄引用的对象。 您必须显式地删除对象,或者允许垃圾回收器在确定不再使用对象之后立即释放内存,因为这些对象驻留在托管堆上。