克隆智能指针(clone_ptr)
一个行为类似于引用变量的智能指针。
引言
clone_ptr
类是一个智能指针,设计用于与 STL 容器一起使用,以便对对象本身进行比较和复制操作,而不是对象的地址。 它通过在执行这些操作时充当引用变量来实现这一点。 赋值操作会创建对象的唯一副本,因为 clone_ptr
表现为引用变量,并且它是一个非共享智能指针。
它能够将自身复制到正确的派生类型,而无需基类具有克隆方法。 此外,克隆指针具有用于赋值、等于和大于的运算符方法,这些方法调用对象的赋值运算符。 这意味着当对克隆指针的容器执行排序时,将对指向的对象进行排序,而不是指针地址。
与共享指针不同,clone_ptr
类不共享其指针,并且它基于严格的指针所有权逻辑。 这意味着一个克隆指针容器的内容可以复制到另一个容器,而无需两个容器指向相同的数据。
背景
我决定创建一个新的智能指针,因为常见的可用智能指针不太适合 STL 容器。 按照 C++ 标准,std::auto_ptr
根本不能与 STL 容器一起使用。 如果您需要将对象从一个容器复制到另一个容器,boost 的 shared_ptr
类并不是一个好的选择。
clone_ptr
类与其他智能指针(如 boost::shared_ptr
和 auto_ptr
)之间的一个主要逻辑区别是,clone_ptr
试图通过指针接口模拟引用变量(具体类型),而 boost::shared_ptr
和 std::auto_ptr
试图模拟指针。 模拟指针的缺点是它引入了通常没有益处,并且通常不利于所需效果的逻辑。
以下是容器中不需要的指针行为的示例。
vector<std::shared_ptr<std::string> > vStrings;
vStrings.push_back(std::make_shared<string>(std::string("Jill")));
vStrings.push_back(std::make_shared<string>(std::string("Tom")));
vStrings.push_back(std::make_shared<string>(std::string("David")));
vStrings.emplace_back(new std::string("Zulma"));
vStrings.emplace_back(new std::string("Yari"));
vStrings.emplace_back(new std::string("Bill"));
sort(vStrings.begin(), vStrings.end()); //Will NOT sort objects
//Displays unsorted data
for (size_t i = 0; i < vStrings.size(); ++i)
cout << vStrings[i]->c_str() << "\n";
通常,在上面的代码中,期望的逻辑是对容器中的对象进行排序,但对于大多数智能指针,将对对象地址进行排序。 结果是输出的结果没有按名称排序。
使用 clone_ptr
,比较是在指向的对象上进行的,而不是在指针的地址上进行的。 以下代码将输出按排序的名称。
vector<clone_ptr<string> > vStrings;
vStrings.push_back(new string("Jill"));
vStrings.push_back(new string("Tom"));
vStrings.push_back(new string("David"));
vStrings.emplace_back(new string("Zulma"));
vStrings.emplace_back(new string("Yari"));
vStrings.emplace_back(new string("Bill"));
sort(vStrings.begin(), vStrings.end()); //Sorts the object
//Display sorted data based on the object
for (size_t i = 0; i < vStrings.size(); ++i)
cout << vStrings[i]->c_str() << "\n";
在智能指针的容器中,很少需要或期望对指针地址进行比较,而是更有益和实用的是让运算符将逻辑应用于解引用指针,而不是指针本身。 这就是 clone_ptr
类不尝试 100% 指针模拟,而是更像是混合体的原因,在有益时模拟指针,但在应用比较运算符时模拟引用变量类型。
尽管 clone_ptr
类是专门为与 STL 容器一起使用而设计的,但它仍然有用,并且在 STL 容器之外具有实际应用。
Using the Code
使用 clone_ptr
比其他智能指针有很多好处,但以下内容突出显示了一些主要好处。
clone_ptr
允许复制对象,而不是复制指针地址。
void CopyCorrectDerivedTypeDemo() {
std::vector < clone_ptr < BaseClass> > vBaseClass;
//Setup data using base and derived type classes
vBaseClass.push_back(new BaseClass( "1" ));
vBaseClass.push_back(new Derived_B( "2" ));
vBaseClass.push_back(new BaseClass( "3" ));
vBaseClass.push_back(new Derived_A( "4" ));
vBaseClass.push_back(new BaseClass( "5" ));
//Copy contents from one container to another
std::vector < clone_ptr < BaseClass > >
vBaseClass_Copy(vBaseClass.begin(), vBaseClass.end());
//Display results
for (std::vector < clone_ptr < BaseClass > >::size_type i
= 0;i < vBaseClass_Copy.size();++i)
{
vBaseClass_Copy[i]->WhoAmI();
}
对 clone_ptr
的容器进行排序时,将对对象类型进行排序,而不是对指针地址进行排序。
void VectorSortDemo()
{
std::vector < clone_ptr < BaseClass> > vBaseClass;
//Setup data using base and derived type classes
vBaseClass.push_back(new BaseClass( "3" ));
vBaseClass.push_back(new Derived_B( "2" ));
vBaseClass.push_back(new BaseClass( "1" ));
vBaseClass.push_back(new Derived_A( "5" ));
vBaseClass.push_back(new BaseClass( "4" ));
//Sort Data
sort(vBaseClass.begin(), vBaseClass.end());
//Display sorted data
for (std::vector < clone_ptr < BaseClass > >::size_type
i = 0;i < vBaseClass.size();++i)
{
vBaseClass[i]->WhoAmI();
}
使用 clone_ptr
时,std::set
和 std::map
容器也会被正确排序。
void SetDemo()
{
std::set < clone_ptr < BaseClass > > sBaseClass;
//Setup data using base and derived type classes
sBaseClass.insert(new BaseClass( "3" ));
sBaseClass.insert(new Derived_B( "2" ));
sBaseClass.insert(new BaseClass( "1" ));
sBaseClass.insert(new Derived_A( "5" ));
sBaseClass.insert(new BaseClass( "4" ));
//Display sorted data
for(std::set < clone_ptr < BaseClass > >::iterator i =
sBaseClass.begin();i!=sBaseClass.end();++i)
{
(*i)->WhoAmI();
}
相等运算符有助于确定一个容器是否等于另一个容器。
void ContainerEqualityDemo()
{
list < clone_ptr < BaseClass > > PtrsX;
PtrsX.push_back(new BaseClass("1"));
PtrsX.push_back(new BaseClass("2"));
PtrsX.push_back(new BaseClass("3"));
list < clone_ptr < BaseClass > > PtrsY;
PtrsY.push_back(new Derived_B("4"));
PtrsY.push_back(new Derived_B("5"));
PtrsY.push_back(new Derived_B("6"));
list < clone_ptr < BaseClass > > PtrsZ;
PtrsZ.push_back(new Derived_A("1"));
PtrsZ.push_back(new Derived_A("2"));
PtrsZ.push_back(new Derived_A("3"));
bool IsEqual1 = (PtrsX == PtrsY); //Should be false
bool IsEqual2 = (PtrsX == PtrsZ); //Should be true
cout << "Should be false (0) IsEqual1 = " << IsEqual1 << endl;
cout << "Should be true (1) IsEqual2 = " << IsEqual2 << endl;
相等运算符也有助于 STL 的替换和删除功能。
void RemoveFromListDemo()
{
std::list < clone_ptr < BaseClass> > lBaseClass;
lBaseClass.push_back(new BaseClass( "X" ));
lBaseClass.push_back(new Derived_B( "2" ));
lBaseClass.push_back(new BaseClass( "X" ));
lBaseClass.push_back(new Derived_A( "5" ));
lBaseClass.push_back(new BaseClass( "4" ));
//Remove certain values
lBaseClass.remove(clone_ptr < BaseClass > (new BaseClass("X"))); //Remove all X's
//Display results
for(std::list < clone_ptr < BaseClass > >::iterator
i = lBaseClass.begin();i!=lBaseClass.end();++i)
{
(*i)->WhoAmI();
}
优点和缺点
缺点
复制容器时,shared_ptr
可能比 clone_ptr
使用更少的内存,因为它复制的是地址,而不是复制对象。
优点
在容器中复制对象时,会创建唯一的副本,可以在不影响原始对象的情况下对其进行操作。
产生预期的比较行为,因为比较运算符在对象上执行,而不是在指针上执行。
可以在 std::map
和 std::set
中使用,以根据对象获得排序结果。
与 std::sort
一起使用并产生预期的行为。
结论
通过使用 clone_ptr
,您可以获得指针和智能指针的优势,而没有排序问题和无法创建独立副本的缺点。
以上所有代码都在用于下载的演示项目中列出。
历史
- 2021 年 7 月 21 日
- 更新的使用示例
- 添加了优缺点
- 添加了澄清,主要问题是引用逻辑而不是指针逻辑