9.7 KiB
9.7 KiB
std::unique_ptr<>
std::unique_ptr<>
- one object == one owner
- destructor destroys the object
- copying not allowed
- moving allowed
- can use custom deleter
- 0 cost class -> no impact on efficiency
std::unique_ptr<> usage
- Old style approach vs modern approach
#include <iostream> // old-style approach
struct Msg {
int getValue() { return 42; }
};
Msg* createMsg() {
return new Msg{};
}
int main() {
auto msg = createMsg();
std::cout << msg->getValue();
delete msg;
}
#include <memory> // modern approach
#include <iostream>
struct Msg {
int getValue() { return 42; }
};
std::unique_ptr<Msg> createMsg() {
return std::make_unique<Msg>();
}
int main() {
// unique ownership
auto msg = createMsg();
std::cout << msg->getValue();
}
std::unique_ptr<> usage
- Copying is not allowed
- Moving is allowed
std::unique_ptr<MyData> source(void);
void sink(std::unique_ptr<MyData> ptr);
void simpleUsage() {
source();
sink(source());
auto ptr = source();
// sink(ptr); // compilation error
sink(std::move(ptr));
auto p1 = source();
// auto p2 = p1; // compilation error
auto p2 = std::move(p1);
// p1 = p2; // compilation error
p1 = std::move(p2);
}
std::unique_ptr<MyData> source(void);
void sink(std::unique_ptr<MyData> ptr);
void collections() {
std::vector<std::unique_ptr<MyData>> v;
v.push_back(source());
auto tmp = source();
// v.push_back(tmp); // compilation error
v.push_back(std::move(tmp));
// sink(v[0]); // compilation error
sink(std::move(v[0]));
}
std::unique_ptr<> problem with containers
What is wrong with this part of code?
std::unique_ptr<MyData> source(void);
void sink(std::unique_ptr<MyData> ptr);
void collections() {
std::vector<std::unique_ptr<MyData>> v;
v.push_back(source());
auto tmp = source();
v.push_back(std::move(tmp));
sink(std::move(v[0]));
std::cout << *(v[0]) << '\n';
}
std::unique_ptr<> cooperation with raw pointers
#include <memory>
void legacyInterface(int*) {}
void deleteResource(int* p) { delete p; }
void referenceInterface(int&) {}
int main() {
auto ptr = std::make_unique<int>(5);
legacyInterface(ptr.get());
deleteResource(ptr.release());
ptr.reset(new int{10});
referenceInterface(*ptr);
ptr.reset(); // ptr is a nullptr
return 0;
}
-
get()– returns a raw pointer without releasing the ownership -
release()– returns a raw pointer and release the ownership -
reset()– replaces the manager object -
operator*()– dereferences pointer to the managed object
std::make_unique()
#include <memory>
struct Msg {
Msg(int i) : value(i) {}
int value;
};
int main() {
auto ptr1 = std::unique_ptr<Msg>(new Msg{5});
auto ptr2 = std::make_unique<Msg>(5); // equivalent to above
return 0;
}
std::make_unique() is a factory function that produce unique_ptrs
- added in C++14 for symmetrical operations on unique and shared pointers
-
avoids bare
newexpression
std::unique_ptr<T[]>
struct MyData {};
void processPointer(MyData* md) {}
void processElement(MyData md) {}
using Array = std::unique_ptr<MyData[]>;
void use(void)
{
Array tab{new MyData[42]};
processPointer(tab.get());
processElement(tab[13]);
}
-
During destruction
-
std::unique_ptr<T>callsdelete -
std::unique_ptr<T[]>callsdelete[]
-
-
std::unique_ptr<T[]>has additionaloperator[]for accessing array element -
Usually
std::vector<T>is a better choice
Exercise: Resource
- Compile and run Resource application
- Check memory leaks under valgrind
-
Fix memory leaks with a proper usage of
deleteoperator -
Refactor the solution to use
std::unique_ptr<> -
Use
std::make_unique()
Exercise: Converter
- Compile and run Converter application and check memory leaks under valgrind
- Fix code using std::unique_ptr and std::make_unique
- Find other issues and fix them (use good practise etc...)
Why virtual D'tor is so important (1)?
class Resource {
public:
explicit Resource(const std::string& str): str_(str) {}
const std::string& str() const {
return str_;
}
private:
std::string str_;
};
class Converter {
public:
Converter() {
std::cout << "C'tor converter\n";
}
virtual ~Converter() {
std::cout << "D'tor converter\n";
}
virtual void Convert(const std::unique_ptr<Resource>& resource) const = 0;
};
class CurlyBracketConverter : public Converter {
public:
CurlyBracketConverter() {
std::cout << "C'tor CurlyBracketConverter\n";
}
~CurlyBracketConverter() override {
std::cout << "D'tor CurlyBracketConverter\n";
}
void Convert(const std::unique_ptr<Resource>& resource) const override {
std::cout << "{" << resource->str() << "}\n";
}
};
class SquareBracketConverter : public Converter {
public:
SquareBracketConverter() {
std::cout << "C'tor SquareBracketConverter\n";
}
~SquareBracketConverter() override {
std::cout << "D'tor SquareBracketConverter\n";
}
virtual void Convert(const std::unique_ptr<Resource>& resource) const override{
std::cout << "[" << resource->str() << "]\n";
}
};
class Printer {
public:
explicit Printer(std::unique_ptr<Converter> converter): converter_(std::move(converter)) {}
void Print(const std::unique_ptr<Resource>& resource) const {
converter_->Convert(resource);
}
private:
std::unique_ptr<Converter> converter_;
};
int main() {
auto resource = std::make_unique<Resource>("Ala has a cat");
Printer printer(std::make_unique<SquareBracketConverter>());
Printer printer2(std::make_unique<CurlyBracketConverter>());
return 0;
}
Why virtual D'tor is so important (2)?
C'tor converter
C'tor SquareBracketConverter
C'tor converter
C'tor CurlyBracketConverter
D'tor converter
D'tor converter
Try to add virtual to your D'tor and check result
C'tor converter
C'tor SquareBracketConverter
C'tor converter
C'tor CurlyBracketConverter
D'tor CurlyBracketConverter
D'tor converter
D'tor SquareBracketConverter
D'tor converter
Custom Deleter
- When there is a special way to delete object
- Type of unique_ptr change!
class Foo {
public:
Foo() {
std::cout << "Foo C'tor\n";
}
void print() const {
std::cout << "Foo!\n";
}
private:
// For some reason, we allow only this function to delete object
friend void deleteMe(Foo* const foo);
~Foo() {
std::cout << "Foo D'tor\n";
}
};
void deleteMe(Foo* const foo) {
std::cout << "Delete object Foo!\n";
delete foo;
}
int main() {
// Can't use make unique, need to use unique_ptr C'tor
// unique_ptr(pointer __p, const deleter_type& __d) noexcept
std::unique_ptr<Foo, void(*)(Foo* const)> ptr(new Foo, deleteMe);
ptr->print();
return 0;
}
Output
Foo C'tor
Foo!
Delete object Foo!
Foo D'tor