user2545918
user2545918

Reputation:

Smart pointers in C++ APIs?

It is fairly often suggested not to use raw pointers in modern C++, except for a few rare cases. What is the common practice of using smart pointers in C++ library APIs?

The following use cases come up to my mind:

  1. A function which returns a new object.
  2. A function which returns a new object, but it has also created another reference to this object.
  3. A function which only uses an object it receives as an argument.
  4. A function which takes over the ownership of an object.
  5. A function which will store a reference to an object which it has received as an argument, but there might be other references to the very same object (from the callers side).

Upvotes: 7

Views: 1953

Answers (3)

kervin
kervin

Reputation: 11858

This is a very old question but also an important one.

One school of thought missing from the answers is that you should not return raw nor smart pointers from your API if you can help it.

The raw pointer issue has been discussed. But smart pointers can also be misused, especially in larger projects with complex internal APIs.

A workaround is creating an RAII/wrapper object that holds the ( never-exposed ) smart pointer.

Upvotes: 2

Matthieu M.
Matthieu M.

Reputation: 299850

Unfortunately, a library API design goes far beyond the rule of the language itself: suddenly you have to care about implementations details such as the ABI.

Contrary to C, where common implementations can easily interact together, C++ implementations have very different ABIs (Microsoft ABI for VC++ is completely incompatible with the Itanium ABI used by gcc and Clang), and C++ Standard Library implementations are also incompatible with each others, so that a library compiled with libstdc++ (bundled with gcc) cannot be used by a program using another major version of libstdc++ or another implementation such as libc++ (bundled with Clang) if std:: classes appear in the interface.

Therefore, it really depends on whether you intend your library to be delivered as a binary or assume the user will be in a position to compile the library with its own compiler and Standard Library implementation of choice. Only in the latter case should you expose a C++ interface, for binary distributions sticking to C is better (and it's also easier to integrate with other languages).


With that out of the way, let's assume you decided to use a C++ API:

1) A function which returns a new object.

If the object can be returned by value, do so; if it is polymorphic, use std::unique_ptr<Object>.

2) A function which returns a new object, but it has also created another reference to this object.

Rare Case. I suppose that you mean it has kept a reference, somehow. In this case you have shared ownership so the obvious choice is std::shared_ptr<Object>.

3) A function which only uses an object it receives as an argument.

Much more complex than it first seems, even supposing no reference to the object is kept.

  • in general, pass by reference (const or not)
  • unless you intend to operate on a copy of the object, in which case pass by value to benefit from a potential move

4) A function which takes over the ownership of an object.

Simple ownership: std::unique_ptr<Object>.

5) A function which will store a reference to an object which it has received as an argument, but there might be other references to the very same object (from the callers side).

Shared ownership: std::shared_ptr<Object>

Upvotes: 13

user2545918
user2545918

Reputation:

  1. Unique pointers may do the job in the general case.

    std::unique_ptr<Foo> func();
    

    So that you can assign them directly to auto variables at the callers site, and not worry about memory management.

    auto u = func();
    

    In case you need shared ownership, you can convert smart pointers on the callers site:

    std::shared_ptr<Foo> s{func()};
    

    However, in case the return type of the function is not polymorphic, and that type is fast to move, you may prefer return by value:

    Foo func();
    
  2. Shared pointers.

    std::shared_ptr<Foo> func();
    
  3. Simply use a reference or const reference to the object, with raw pointers or smart pointers "dereferenced" at the callers site.

    void func(Foo& obj);
    void func(const Foo& obj);
    

    In case the parameters are optional, you may use raw pointers, so that you can easily pass a nullptr to them.

    void func(Foo *obj);
    void func(const Foo *obj);
    
  4. Unique pointers.

    void func(std::unique_ptr<Foo> obj);
    
  5. Const reference to shared pointers.

    void func(const std::shared_ptr<Foo>& obj);
    

    The const reference (to shared pointer) is simply an optimization to prevent adjusting the reference count when the function is called and when it returns.

Upvotes: 7

Related Questions