如何传递 std: : special_ptr?

我第一次尝试使用 C + + 11 unique_ptr; 我正在替换我的一个项目中的一个多态原始指针,它属于一个类,但是经常传递。

我以前有这样的功能:

bool func(BaseClass* ptr, int other_arg) {
bool val;
// plain ordinary function that does something...
return val;
}

但我很快意识到,我不能转向:

bool func(std::unique_ptr<BaseClass> ptr, int other_arg);

因为调用者必须处理函数的指针所有权,这是我不想做的。那么,我的问题的最佳解决方案是什么?

我想传递指针作为参考,像这样:

bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);

但是我觉得这样做非常不舒服,首先,因为传递一些已经输入为 _ptr的内容作为引用似乎是非本能的,这些内容将是一个引用的引用。其次,因为函数签名变得更大了。第三,因为在生成的代码中,需要两个连续的间接指针才能到达我的变量。

77571 次浏览

If you want the function to use the pointee, pass a reference to it. There's no reason to tie the function to work only with some kind of smart pointer:

bool func(BaseClass& base, int other_arg);

And at the call site use operator*:

func(*some_unique_ptr, 42);

Alternatively, if the base argument is allowed to be null, keep the signature as is, and use the get() member function:

bool func(BaseClass* base, int other_arg);
func(some_unique_ptr.get(), 42);

I agree with Martinho, but I think it is important to point out the ownership semantics of a pass-by-reference. I think the correct solution is to use a simple pass-by-reference here:

bool func(BaseClass& base, int other_arg);

The commonly accepted meaning of a pass-by-reference in C++ is like as if the caller of the function tells the function "here, you can borrow this object, use it, and modify it (if not const), but only for the duration of the function body." This is, in no way, in conflict with the ownership rules of the unique_ptr because the object is merely being borrowed for a short period of time, there is no actual ownership transfer happening (if you lend your car to someone, do you sign the title over to him?).

So, even though it might seem bad (design-wise, coding practices, etc.) to pull the reference (or even the raw pointer) out of the unique_ptr, it actually is not because it is perfectly in accordance with the ownership rules set by the unique_ptr. And then, of course, there are other nice advantages, like clean syntax, no restriction to only objects owned by a unique_ptr, and so.

The advantage of using std::unique_ptr<T> (aside from not having to remember to call delete or delete[] explicitly) is that it guarantees that a pointer is either nullptr or it points to a valid instance of the (base) object. I will come back to this after I answer your question, but the first message is DO use smart pointers to manage the lifetime of dynamically allocated objects.

Now, your problem is actually how to use this with your old code.

My suggestion is that if you don't want to transfer or share ownership, you should always pass references to the object. Declare your function like this (with or without const qualifiers, as needed):

bool func(BaseClass& ref, int other_arg) { ... }

Then the caller, which has a std::shared_ptr<BaseClass> ptr will either handle the nullptr case or it will ask bool func(...) to compute the result:

if (ptr) {
result = func(*ptr, some_int);
} else {
/* the object was, for some reason, either not created or destroyed */
}

This means that any caller has to promise that the reference is valid and that it will continue to be valid throughout the execution of the function body.


Here is the reason why I strongly believe you should not pass raw pointers or references to smart pointers.

A raw pointer is only a memory address. Can have one of (at least) 4 meanings:

  1. The address of a block of memory where your desired object is located. (the good)
  2. The address 0x0 which you can be certain is not dereferencable and might have the semantics of "nothing" or "no object". (the bad)
  3. The address of a block of memory which is outside of the addressable space of your process (dereferencing it will hopefully cause your program to crash). (the ugly)
  4. The address of a block of memory which can be dereferenced but which doesn't contain what you expect. Maybe the pointer was accidentally modified and now it points to another writable address (of a completely other variable within your process). Writing to this memory location will cause lots of fun to happen, at times, during the execution, because the OS will not complain as long as you are allowed to write there. (Zoinks!)

Correctly using smart pointers alleviates the rather scary cases 3 and 4, which are usually not detectable at compile time and which you generally only experience at runtime when your program crashes or does unexpected things.

Passing smart pointers as arguments has two disadvantages: you cannot change the const-ness of the pointed object without making a copy (which adds overhead for shared_ptr and is not possible for unique_ptr), and you are still left with the second (nullptr) meaning.

I marked the second case as (the bad) from a design perspective. This is a more subtle argument about responsibility.

Imagine what it means when a function receives a nullptr as its parameter. It first has to decide what to do with it: use a "magical" value in place of the missing object? change behavior completely and compute something else (which doesn't require the object)? panic and throw an exception? Moreover, what happens when the function takes 2, or 3 or even more arguments by raw pointer? It has to check each of them and adapt its behavior accordingly. This adds a whole new level on top of input validation for no real reason.

The caller should be the one with enough contextual information to make these decisions, or, in other words, the bad is less frightening the more you know. The function, on the other hand, should just take the caller's promise that the memory it is pointed to is safe to work with as intended. (References are still memory addresses, but conceptually represent a promise of validity.)

Personally, I avoid pulling a reference from a pointer/smart pointer. Because what happens if the pointer is nullptr? If you change the signature to this:

bool func(BaseClass& base, int other_arg);

You might have to protect your code from null pointer dereferences:

if (the_unique_ptr)
func(*the_unique_ptr, 10);

If the class is the sole owner of the pointer, the second of Martinho's alternative seems more reasonable:

func(the_unique_ptr.get(), 10);

Alternatively, you can use std::shared_ptr. However, if there's one single entity responsible for delete, the std::shared_ptr overhead does not pay off.