【问题标题】:Replacing/refactoring of naked pointers替换/重构裸指针
【发布时间】:2021-08-24 15:23:40
【问题描述】:

我想在类继承情况下替换传统的裸指针用法。

我的意思的例子:

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

class Base
{
public:
  void doBaseStuff ()
  {
    cout << "base stuff\n";
  }
};

class Derived:public Base
{
public:
  void doDerivedStuff ()
  {
    cout << "derived stuff\n";
  }
};


int main ()
{
 // vector to hold pointers
  vector < Base * >baseCollection;

// generate pointers
  Derived *derivedPtr = new Derived;
  Base* basePtr = new Base;
  
 // use derived pointer for something
  derivedPtr->doDerivedStuff ();
  
 // fill vector with pointers
  baseCollection.push_back (basePtr);
  baseCollection.push_back (derivedPtr);

 // iterate some 'interface' method
 for (auto & element:baseCollection)
 {
   element->doBaseStuff ();
 }

  return 0;
}

如下所示的重构方法是否正确?我使用 C++17。 std::unique_ptr 而不是 std::shared_ptr 在这种情况下不会编译(行:baseCollection.push_back(derivedPtr);)。

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

// sample classes
class Base
{
public:
  void doBaseStuff ()
  {
    cout << "base stuff\n";
  }
};

class Derived:public Base
{
public:
  void doDerivedStuff ()
  {
    cout << "derived stuff\n";
  }
};


int main ()
{
 // vector to hold pointers
 vector<shared_ptr<Base>> baseCollection;
 
 // generate pointers
 shared_ptr<Base> basePtr = make_shared<Base>();
 shared_ptr<Derived> derivedPtr = make_shared<Derived>();
 
 // use derived pointer for something
 derivedPtr->doDerivedStuff();
 
 // fill vector with pointers
 baseCollection.push_back(basePtr);
 baseCollection.push_back(derivedPtr); // implicit cast from shared_ptr<Derived> to shared_ptr<Base> !?
 
 // iterate some 'interface' method
 for(auto& element : baseCollection)
 {
     element->doBaseStuff();
 }

  return 0;
}

正如预期的那样,两种情况下的输出如下:

derived stuff
base stuff
base stuff

【问题讨论】:

  • baseCollection.push_back( std::move(derivedPtr) ); 将允许derivedPtr 成为std::unique_ptr。 [意见警告]std::shared_ptr 往往速度较慢,并且往往使代码更难推理。 std::unique_ptr应该是你可以使用的首选。
  • @DrewDormann 据我了解,unique_ptr 会阻止自身被复制,因此可以保证存在指向同一对象的“被遗忘”指针......
  • 这样可以保证“被遗忘”的指向同一个对象的指针存在我不确定你在这里的意思。使用 unique_ptr 表示所有权,使用裸指针表示非所有权。
  • @Eljay 所有权概念实际上是我的意思。谢谢!

标签: c++ smart-pointers


【解决方案1】:

您可以使用std::unique_ptr 代替std::shared_ptr(确实推荐,因为所有权在您的应用程序中明确处理,而不是未指定给种类垃圾收集器),但是您必须将它们移动到向量中才能转移(唯一)所有权。

 vector<unique_ptr<Base>> baseCollection;
 
 // generate pointers
 unique_ptr<Base> basePtr = make_unique<Base>();
 unique_ptr<Derived> derivedPtr = make_unique<Derived>();
 
 // use derived pointer for something
 derivedPtr->doDerivedStuff();
 
 // fill vector with pointers
 baseCollection.push_back(std::move(basePtr));
 baseCollection.push_back(std::move(derivedPtr));

当然,您以后不能再使用basePtrderivedPtr 变量,因为它们不再拥有这些对象。

另一种方法是使用临时人员:

 baseCollection.push_back(make_unique<Base>());
 baseCollection.push_back(make_unique<Derived>());

请注意,在处理 unique_ptrs 时,您仍然可以使用原始指针(使用 .get() 成员函数),但含义不同:我知道一个对象是 拥有由其他人(unique_ptr 此处)。

并且正如cmets中所说,为了保证正确的析构,Base的析构函数应该是virtual ~Base()=default;,因为baseCollection的析构函数并不知道Base指针实际上指向了一个@ 987654334@(可能大小不一样,需要动态调度)。这与unique_ptrshared_ptr 无关;原始指针存在问题。 更进一步,由于我们有多态类型,我们应该禁用 (=delete) 复制/移动构造函数/分配以防止切片,但我们离最初的问题还很远。

【讨论】:

  • 这个程序的行为是不确定的,因为它通过Base*类型的指针删除了Derived类型的对象。
  • 啊,关键是,只有一个指针存在(在这种情况下,在 std::vector 中)?我懂了!所以 unique_ptr / std::move 通常是这种情况下的最佳实践?
  • @VinzentMeier unique_ptr 传达“此指针拥有指向的数据。” shared_ptr 传达“不清楚或难以确定数据的所有权”
【解决方案2】:

如下图所示的重构方法正确吗?

重构程序的行为与第一个不同。第一个程序会泄漏内存,而重构的程序不会。这当然是一件好事,只要是有意为之。

我使用 C++17。在这种情况下,std::unique_ptr 而不是 std::shared_ptr 不会编译。

那是因为你复制了指针,而唯一指针是不可复制的。您可以改为移动指针。但是,指向基的唯一指针还有一个共享指针没有的问题:如果通过指向基的指针删除派生对象,并且基的析构函数不是虚拟的,那么程序的行为将是未定义的。这可以通过将基础的析构函数设为虚拟来轻松解决。

因此,只要不复制指针,并且将析构函数设为虚拟,就无需支付共享所有权的开销。

【讨论】:

    猜你喜欢
    • 2014-05-25
    • 2012-03-07
    • 1970-01-01
    • 2019-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多