Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

}

Since std::weak_ptr does not keep its referenced object alive, direct data access through a std::weak_ptr is not

possible. Instead it provides a lock() member function that attempts to retrieve a std::shared_ptr to the referenced object:

#include <cassert> #include <memory> int main()

{

{

std::weak_ptr<int> wk; std::shared_ptr<int> sp;

{

std::shared_ptr<int> sh = std::make_shared<int>(42); wk = sh;

//calling lock will create a shared_ptr to the object referenced by wk sp = wk.lock();

//sh will be destroyed after this point, but sp is still alive

}

//sp still keeps the data alive.

//At this point we could even call lock() again

//to retrieve another shared_ptr to the same data from wk assert(*sp == 42);

assert(!wk.expired());

//resetting sp will delete the data,

//as it is currently the last shared_ptr with ownership sp.reset();

//attempting to lock wk now will return an empty shared_ptr,

//as the data has already been deleted

sp = wk.lock(); assert(!sp); assert(wk.expired());

}

}

Section 33.4: Using custom deleters to create a wrapper to a C interface

Many C interfaces such as SDL2 have their own deletion functions. This means that you cannot use smart pointers directly:

std::unique_ptr<SDL_Surface> a; // won't work, UNSAFE!

Instead, you need to define your own deleter. The examples here use the SDL_Surface structure which should be freed using the SDL_FreeSurface() function, but they should be adaptable to many other C interfaces.

The deleter must be callable with a pointer argument, and therefore can be e.g. a simple function pointer:

std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);

Any other callable object will work, too, for example a class with an operator():

struct SurfaceDeleter {

void operator()(SDL_Surface* surf) { SDL_FreeSurface(surf);

}

};

GoalKicker.com – C++ Notes for Professionals

163

std::unique_ptr<SDL_Surface, SurfaceDeleter> a(pointer, SurfaceDeleter{}); // safe std::unique_ptr<SDL_Surface, SurfaceDeleter> b(pointer); // equivalent to the above

// as the deleter is value-initialized

This not only provides you with safe, zero overhead (if you use unique_ptr) automatic memory management, you also get exception safety.

Note that the deleter is part of the type for unique_ptr, and the implementation can use the empty base optimization to avoid any change in size for empty custom deleters. So while std::unique_ptr<SDL_Surface, SurfaceDeleter> and std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> solve the same problem in a similar way, the former type is still only the size of a pointer while the latter type has to hold two pointers: both the SDL_Surface* and the function pointer! When having free function custom deleters, it is preferable to wrap the function in an empty type.

In cases where reference counting is important, one could use a shared_ptr instead of an unique_ptr. The shared_ptr always stores a deleter, this erases the type of the deleter, which might be useful in APIs. The disadvantages of using shared_ptr over unique_ptr include a higher memory cost for storing the deleter and a performance cost for maintaining the reference count.

// deleter required at construction time and is part of the type std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);

// deleter is only required at construction time, not part of the type std::shared_ptr<SDL_Surface> b(pointer, SDL_FreeSurface);

Version ≥ C++17

With template auto, we can make it even easier to wrap our custom deleters:

template <auto DeleteFn> struct FunctionDeleter { template <class T>

void operator()(T* ptr) { DeleteFn(ptr);

}

};

template <class T, auto DeleteFn>

using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;

With which the above example is simply:

unique_ptr_deleter<SDL_Surface, SDL_FreeSurface> c(pointer);

Here, the purpose of auto is to handle all free functions, whether they return void (e.g. SDL_FreeSurface) or not (e.g. fclose).

Section 33.5: Unique ownership without move semantics (auto_ptr)

Version < C++11

NOTE: std::auto_ptr has been deprecated in C++11 and will be removed in C++17. You should only use this if you are forced to use C++03 or earlier and are willing to be careful. It is recommended to move to unique_ptr in combination with std::move to replace std::auto_ptr behavior.

GoalKicker.com – C++ Notes for Professionals

164

Before we had std::unique_ptr, before we had move semantics, we had std::auto_ptr. std::auto_ptr provides unique ownership but transfers ownership upon copy.

As with all smart pointers, std::auto_ptr automatically cleans up resources (see RAII):

{

std::auto_ptr<int> p(new int(42)); std::cout << *p;

} // p is deleted here, no memory leaked

but allows only one owner:

std::auto_ptr<X> px = ...; std::auto_ptr<X> py = px;

// px is now empty

This allows to use std::auto_ptr to keep ownership explicit and unique at the danger of losing ownership unintended:

void f(std::auto_ptr<X> ) {

//assumes ownership of X

//deletes it at end of scope

};

std::auto_ptr<X> px = ...;

f(px); // f acquires ownership of underlying X // px is now empty

px->foo(); // NPE!

// px.~auto_ptr() does NOT delete

The transfer of ownership happened in the "copy" constructor. auto_ptr's copy constructor and copy assignment operator take their operands by non-const reference so that they could be modified. An example implementation might be:

template <typename T> class auto_ptr {

T* ptr; public:

auto_ptr(auto_ptr& rhs) : ptr(rhs.release())

{ }

auto_ptr& operator=(auto_ptr& rhs) { reset(rhs.release());

return *this;

}

T* release() {

T* tmp = ptr; ptr = nullptr; return tmp;

}

void reset(T* tmp = nullptr) { if (ptr != tmp) {

delete ptr; ptr = tmp;

}

}

GoalKicker.com – C++ Notes for Professionals

165

/* other functions ... */

};

This breaks copy semantics, which require that copying an object leaves you with two equivalent versions of it. For any copyable type, T, I should be able to write:

T a = ...; T b(a);

assert(b == a);

But for auto_ptr, this is not the case. As a result, it is not safe to put auto_ptrs in containers.

Section 33.6: Casting std::shared_ptr pointers

It is not possible to directly use static_cast, const_cast, dynamic_cast and reinterpret_cast on std::shared_ptr to retrieve a pointer sharing ownership with the pointer being passed as argument. Instead, the functions std::static_pointer_cast, std::const_pointer_cast, std::dynamic_pointer_cast and std::reinterpret_pointer_cast should be used:

struct Base { virtual ~Base() noexcept {}; }; struct Derived: Base {};

auto derivedPtr(std::make_shared<Derived>());

auto basePtr(std::static_pointer_cast<Base>(derivedPtr));

auto constBasePtr(std::const_pointer_cast<Base const>(basePtr));

auto constDerivedPtr(std::dynamic_pointer_cast<Derived const>(constBasePtr));

Note that std::reinterpret_pointer_cast is not available in C++11 and C++14, as it was only proposed by N3920 and adopted into Library Fundamentals TS in February 2014. However, it can be implemented as follows:

template <typename To, typename From>

inline std::shared_ptr<To> reinterpret_pointer_cast( std::shared_ptr<From> const & ptr) noexcept

{ return std::shared_ptr<To>(ptr, reinterpret_cast<To *>(ptr.get())); }

Section 33.7: Writing a smart pointer: value_ptr

A value_ptr is a smart pointer that behaves like a value. When copied, it copies its contents. When created, it creates its contents.

// Like std::default_delete: template<class T>

struct default_copier {

// a copier must handle a null T const* in and return null: T* operator()(T const* tin)const {

if (!tin) return nullptr; return new T(*tin);

}

void operator()(void* dest, T const* tin)const { if (!tin) return;

return new(dest) T(*tin);

}

};

//tag class to handle empty case: struct empty_ptr_t {};

constexpr empty_ptr_t empty_ptr{};

//the value pointer type itself:

GoalKicker.com – C++ Notes for Professionals

166

template<class T, class Copier=default_copier<T>, class Deleter=std::default_delete<T>, class Base=std::unique_ptr<T, Deleter>

>

struct value_ptr:Base, private Copier { using copier_type=Copier;

// also typedefs from unique_ptr

using Base::Base;

value_ptr( T const& t ):

Base( std::make_unique<T>(t) ), Copier()

{}

value_ptr( T && t ):

Base( std::make_unique<T>(std::move(t)) ), Copier()

{}

// almost-never-empty: value_ptr():

Base( std::make_unique<T>() ), Copier()

{}

value_ptr( empty_ptr_t ) {}

value_ptr( Base b, Copier c={} ): Base(std::move(b)), Copier(std::move(c))

{}

Copier const& get_copier() const { return *this;

}

value_ptr clone() const { return {

Base( get_copier()(this->get()), this->get_deleter()

), get_copier()

};

}

value_ptr(value_ptr&&)=default; value_ptr& operator=(value_ptr&&)=default;

value_ptr(value_ptr const& o):value_ptr(o.clone()) {} value_ptr& operator=(value_ptr const&o) {

if (o && *this) {

//if we are both non-null, assign contents: **this = *o;

}else {

//otherwise, assign a clone (which could itself be null): *this = o.clone();

}

return *this;

}

value_ptr& operator=( T const& t ) { if (*this) {

**this = t; } else {

*this = value_ptr(t);

}

GoalKicker.com – C++ Notes for Professionals

167