Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best smart pointer return type for a factory function?

With respect to smart pointers and new C++11/14 features, I am wondering what the best-practice return values and function parameter types would be for classes that have these facilities:

  1. A factory function (outside of the class) that creates objects and returns them to users of the class. (For example opening a document and returning an object that can be used to access the content.)

  2. Utility functions that accept objects from the factory functions, use them, but do not take ownership. (For example a function that counts the number of words in the document.)

  3. Functions that keep a reference to the object after they return (like a UI component that takes a copy of the object so it can draw the content on the screen as needed.)

What would the best return type be for the factory function?

  • If it's a raw pointer the user will have to delete it correctly which is problematic.
  • If it returns a unique_ptr<> then the user can't share it if they want to.
  • If it's a shared_ptr<> then will I have to pass around shared_ptr<> types everywhere? This is what I'm doing now and it's causing problems as I'm getting cyclic references, preventing objects from being destroyed automatically.

What is the best parameter type for the utility function?

  • I imagine passing by reference will avoid incrementing a smart pointer reference count unnecessarily, but are there any drawbacks of this? The main one that comes to mind is that it prevents me from passing derived classes to functions taking parameters of the base-class type.
  • Is there some way that I can make it clear to the caller that it will NOT copy the object? (Ideally so that the code will not compile if the function body does try to copy the object.)
  • Is there a way to make it independent of the type of smart pointer in use? (Maybe taking a raw pointer?)
  • Is it possible to have a const parameter to make it clear the function will not modify the object, without breaking smart pointer compatibility?

What is the best parameter type for the function that keeps a reference to the object?

  • I'm guessing shared_ptr<> is the only option here, which probably means the factory class must return a shared_ptr<> also, right?

Here is some code that compiles and hopefully illustrates the main points.

#include <iostream> #include <memory>  struct Document {     std::string content; };  struct UI {     std::shared_ptr<Document> doc;      // This function is not copying the object, but holding a     // reference to it to make sure it doesn't get destroyed.     void setDocument(std::shared_ptr<Document> newDoc) {         this->doc = newDoc;     }     void redraw() {         // do something with this->doc     } };  // This function does not need to take a copy of the Document, so it // should access it as efficiently as possible.  At the moment it // creates a whole new shared_ptr object which I feel is inefficient, // but passing by reference does not work. // It should also take a const parameter as it isn't modifying the // object. int charCount(std::shared_ptr<Document> doc) {     // I realise this should be a member function inside Document, but     // this is for illustrative purposes.     return doc->content.length(); }  // This function is the same as charCount() but it does modify the // object. void appendText(std::shared_ptr<Document> doc) {     doc->content.append("hello");     return; }  // Create a derived type that the code above does not know about. struct TextDocument: public Document {};  std::shared_ptr<TextDocument> createTextDocument() {     return std::shared_ptr<TextDocument>(new TextDocument()); }  int main(void) {     UI display;      // Use the factory function to create an instance.  As a user of     // this class I don't want to have to worry about deleting the     // instance, but I don't really care what type it is, as long as     // it doesn't stop me from using it the way I need to.     auto doc = createTextDocument();      // Share the instance with the UI, which takes a copy of it for     // later use.     display.setDocument(doc);      // Use a free function which modifies the object.     appendText(doc);      // Use a free function which doesn't modify the object.     std::cout << "Your document has " << charCount(doc)         << " characters.\n";      return 0; } 
like image 210
Malvineous Avatar asked Feb 03 '15 08:02

Malvineous


People also ask

Which smart pointer type do you use when cycles need to be broken?

A std::weak_ptr helps to break the cycles of std::shared_ptr (R. 24). These cycles are the reason, a std::shared_ptr will not automatically release its resource.

Which smart pointer should be used when you want a reference counted pointer?

What Pointer Should I Use, and When? As a general rule of thumb, I recommend using std::unique_ptr as your default smart pointer. If you find that you need to share data or utilize reference counting, you should use a std::shared_ptr .

What is smart pointer when should we use it?

A Smart Pointer is a wrapper class over a pointer with an operator like * and -> overloaded. The objects of the smart pointer class look like normal pointers. But, unlike Normal Pointers it can deallocate and free destroyed object memory.


2 Answers

I'll answer in reverse order so to begin with the simple cases.

Utility functions that accept objects from the factory functions, use them, but do not take ownership. (For example a function that counts the number of words in the document.)

If you are calling a factory function, you are always taking ownership of the created object by the very definition of a factory function. I think what you mean is that some other client first obtains an object from the factory and then wishes to pass it to the utility function that does not take ownership itself.

In this case, the utility function should not care at all how ownership of the object it operates on is managed. It should simply accept a (probably const) reference or – if “no object” is a valid condition – a non-owning raw pointer. This will minimize the coupling between your interfaces and make the utility function most flexible.

Functions that keep a reference to the object after they return (like a UI component that takes a copy of the object so it can draw the content on the screen as needed.)

These should take a std::shared_ptr by value. This makes it clear from the function's signature that they take shared ownership of the argument.

Sometimes, it can also be meaningful to have a function that takes unique ownership of its argument (constructors come to mind). Those should take a std::unique_ptr by value (or by rvalue reference) which will also make the semantics clear from the signature.

A factory function (outside of the class) that creates objects and returns them to users of the class. (For example opening a document and returning an object that can be used to access the content.)

This is the difficult one as there are good arguments for both, std::unique_ptr and std::shared_ptr. The only thing clear is that returning an owning raw pointer is no good.

Returning a std::unique_ptr is lightweight (no overhead compared to returning a raw pointer) and conveys the correct semantics of a factory function. Whoever called the function obtains exclusive ownership over the fabricated object. If needed, the client can construct a std::shared_ptr out of a std::unique_ptr at the cost of a dynamic memory allocation.

On the other hand, if the client is going to need a std::shared_ptr anyway, it would be more efficient to have the factory use std::make_shared to avoid the additional dynamic memory allocation. Also, there are situations where you simply must use a std::shared_ptr for example, if the destructor of the managed object is non-virtual and the smart pointer is to be converted to a smart pointer to a base class. But a std::shared_ptr has more overhead than a std::unique_ptr so if the latter is sufficient, we would rather avoid that if possible.

So in conclusion, I'd come up with the following guideline:

  • If you need a custom deleter, return a std::shared_ptr.
  • Else, if you think that most of your clients are going to need a std::shared_ptr anyway, utilize the optimization potential of std::make_shared.
  • Else, return a std::unique_ptr.

Of course, you could avoid the problem by providing two factory functions, one that returns a std::unique_ptr and one that returns a std::shared_ptr so each client can use what best fits its needs. If you need this frequently, I guess you can abstract most of the redundancy away with some clever template meta-programming.

like image 112
5gon12eder Avatar answered Oct 05 '22 21:10

5gon12eder


What would the best return type be for the factory function?

unique_ptr would be best. It prevents accidental leaks, and the user can release ownership from the pointer, or transfer ownership to a shared_ptr (which has a constructor for that very purpose), if they want to use a different ownership scheme.

What is the best parameter type for the utility function?

A reference, unless the program flow is so convoluted that the object might be destroyed during the function call, in which case shared_ptr or weak_ptr. (In either case, it can refer to a base class, and add const qualifiers, if you want that.)

What is the best parameter type for the function that keeps a reference to the object?

shared_ptr or unique_ptr, if you want it to take responsibility for the object's lifetime and not otherwise worry about it. A raw pointer or reference, if you can (simply and reliably) arrange for the object to outlive everything that uses it.

like image 32
Mike Seymour Avatar answered Oct 05 '22 21:10

Mike Seymour