阅读this answer后,看起来最好尽可能使用smart pointers,并将“普通”/原始指针的使用量降至最低。
这是真的吗?
答案 0 :(得分:78)
不,这不是真的。如果一个函数需要一个指针并且与所有权无关,那么我坚信应该传递一个常规指针,原因如下:
shared_ptr
,那么您将无法通过,例如scoped_ptr
规则就是这样 - 如果你知道某个实体必须对该对象拥有某种所有权, 总是 使用智能指针 - 那个给你的你需要的所有权类型。如果没有所有权概念, 永远不会 使用智能指针。
例1:
void PrintObject(shared_ptr<const Object> po) //bad
{
if(po)
po->Print();
else
log_error();
}
void PrintObject(const Object* po) //good
{
if(po)
po->Print();
else
log_error();
}
例2:
Object* createObject() //bad
{
return new Object;
}
some_smart_ptr<Object> createObject() //good
{
return some_smart_ptr<Object>(new Object);
}
答案 1 :(得分:14)
使用智能指针管理所有权是正确的做法。 相反,使用原始指针,无论所有权不,问题都是不错误。
以下是对原始指针的完全合理使用(请记住,总是假设它们是非拥有的):
他们与参考资料竞争
0
std::bind
使用一种约定,其中传递的参数被复制到生成的仿函数中;但是std::bind(&T::some_member, this, ...)
仅复制指针,而std::bind(&T::some_member, *this, ...)
复制对象; std::bind(&T::some_member, std::ref(*this), ...)
是另一种选择他们不与参考
竞争boost::optional<T&>
boost::optional<T&>
提醒一下,编写一个接受智能指针的函数(不是构造函数或函数成员,例如取得所有权)几乎总是错误的,除非它反过来将它传递给构造函数(例如,它是正确的) std::async
因为在语义上它接近于对std::thread
构造函数的调用。如果它是同步的,则不需要智能指针。
回顾一下,这里有一个片段,演示了上述几个用途。我们正在编写并使用一个类,在编写一些输出时将仿函数应用于std::vector<int>
的每个元素。
class apply_and_log {
public:
// C++03 exception: it's acceptable to pass by pointer to const
// to avoid apply_and_log(std::cout, std::vector<int>())
// notice that our pointer would be left dangling after call to constructor
// this still adds a requirement on the caller that v != 0 or that we throw on 0
apply_and_log(std::ostream& os, std::vector<int> const* v)
: log(&os)
, data(v)
{}
// C++0x alternative
// also usable for C++03 with requirement on v
apply_and_log(std::ostream& os, std::vector<int> const& v)
: log(&os)
, data(&v)
{}
// now apply_and_log(std::cout, std::vector<int> {}) is invalid in C++0x
// && is also acceptable instead of const&&
apply_and_log(std::ostream& os, std::vector<int> const&&) = delete;
// Notice that without effort copy (also move), assignment and destruction
// are correct.
// Class invariants: member pointers are never 0.
// Requirements on construction: the passed stream and vector must outlive *this
typedef std::function<void(std::vector<int> const&)> callback_type;
// optional callback
// alternative: boost::optional<callback_type&>
void
do_work(callback_type* callback)
{
// for convenience
auto& v = *data;
// using raw pointers as iterators
int* begin = &v[0];
int* end = begin + v.size();
// ...
if(callback) {
callback(v);
}
}
private:
// association: we use a pointer
// notice that the type is polymorphic and non-copyable,
// so composition is not a reasonable option
std::ostream* log;
// association: we use a pointer to const
// contrived example for the constructors
std::vector<int> const* data;
};
答案 2 :(得分:6)
始终建议使用智能指针,因为它们清楚地记录了所有权。
然而,我们真正想念的是一个“空白”的智能指针,它并不意味着任何所有权概念。
template <typename T>
class ptr // thanks to Martinho for the name suggestion :)
{
public:
ptr(T* p): _p(p) {}
template <typename U> ptr(U* p): _p(p) {}
template <typename SP> ptr(SP const& sp): _p(sp.get()) {}
T& operator*() const { assert(_p); return *_p; }
T* operator->() const { assert(_p); return _p; }
private:
T* _p;
}; // class ptr<T>
这确实是可能存在的任何智能指针的最简单版本:一种记录它不拥有它指向的资源的类型。
答案 3 :(得分:4)
引用计数(特别是shared_ptr使用)将崩溃的一个实例是当您从指针中创建一个循环时(例如,A指向B,B指向A,或A-> B-> C - &gt; A,或等)。在这种情况下,任何对象都不会被自动释放,因为它们都保持彼此的引用计数大于零。
出于这个原因,每当我创建具有父子关系的对象(例如对象树)时,我将使用父对象中的shared_ptrs来保存它们的子对象,但是如果子对象需要指针返回对于他们的父母,我将使用一个普通的C / C ++指针。
答案 4 :(得分:1)
在某些情况下,您可能需要使用指针:
答案 5 :(得分:1)
我认为这里给出了一个更彻底的答案:Which kind of pointer do I use when?
摘自该链接:“使用哑指针(原始指针)或非拥有引用对资源的引用,以及当您知道资源将超过引用时对象/范围。“ (保留原文的粗体)
问题在于,如果您正在编写通用代码,那么绝对肯定该对象将比原始指针更长。考虑这个例子:
struct employee_t {
employee_t(const std::string& first_name, const std::string& last_name) : m_first_name(first_name), m_last_name(last_name) {}
std::string m_first_name;
std::string m_last_name;
};
void replace_current_employees_with(const employee_t* p_new_employee, std::list<employee_t>& employee_list) {
employee_list.clear();
employee_list.push_back(*p_new_employee);
}
void main(int argc, char* argv[]) {
std::list<employee_t> current_employee_list;
current_employee_list.push_back(employee_t("John", "Smith"));
current_employee_list.push_back(employee_t("Julie", "Jones"));
employee_t* p_person_who_convinces_boss_to_rehire_him = &(current_employee_list.front());
replace_current_employees_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
}
令人惊讶的是,replace_current_employees_with()
函数可能会在无意中导致其中一个参数在完成使用之前被释放。
所以尽管最初可能看起来replace_current_employees_with()
函数不需要对其参数的所有权,但它需要某种防御来防止其参数在完成使用之前被隐藏地解除分配的可能性。最简单的解决方案是实际采用(临时共享)参数的所有权,可能是通过shared_ptr
。
但如果你真的不想取得所有权,那么现在有一个安全的选择 - 这是答案的无耻插件部分 - “registered pointers”。 “注册指针”是指向原始指针的智能指针,除了它们在目标对象被销毁时(自动)设置为null_ptr
,并且默认情况下,如果您尝试访问一个对象,它将抛出异常已被删除。
另请注意,已注册的指针可以使用编译时指令“禁用”(自动替换为其原始指针对应部分),从而允许它们仅在调试/测试/测试模式下使用(并产生开销)。所以你真的必须很少使用实际的原始指针。
答案 6 :(得分:0)
我相信即使在原始指针已足够的情况下,也应尽可能使用智能指针。 text ='''Market 05-20 06-20 07-20 08-20
HK 5 5 5 5
US 2 2 2 2
HK 3 3 3 3
UK 7 7 7 7
UK 2 2 2 2'''
import pandas as pd
import io
#df = pd.read_csv("filename.csv")
df = pd.read_csv(io.StringIO(text), sep="\s+")
df = df.groupby("Market").sum()
print(df)
df = df.stack()
print(df)
df = df.reset_index()
print(df)
df.columns = ['Market', 'Data', 'Quantity']
print(df)
可以帮助管理资源生命周期,同时仍保持较小和快速。不要回头!
答案 7 :(得分:-2)
确实如此。我无法看到原始指针优于智能指针的好处,特别是在复杂的项目中。
对于临时和轻量级用法,原始指针很好。