详细介绍std::unique_ptr

详细介绍std::unique_ptr

std::unique_ptr是C++11中引入的智能指针(smart pointer)之一,它提供了一种自动化的资源管理方式,可以避免手动进行内存管理和释放,同时也可以防止内存泄漏和悬挂指针等常见的编程错误。在本文中,我们将详细介绍std::unique_ptr的使用方法和注意事项,并提供一些示例代码。

std::unique_ptr的基本用法

std::unique_ptr是一种独占所有权的智能指针,它只允许一个指针拥有对资源的所有权,其他指针不能访问或者修改这个资源。在创建std::unique_ptr对象时,需要指定指针所指向的资源类型,并使用new操作符来分配资源。例如:

1
2
std::unique_ptr<int> uptr(new int(42));


在这个例子中,我们创建了一个std::unique_ptr对象,它指向一个int类型的资源,并使用new操作符来分配资源。我们可以使用箭头运算符(->)和星号运算符(*)来访问和修改资源,例如:
1
2
3
4
std::cout << *uptr << std::endl; // Output: 42
*uptr = 24;
std::cout << *uptr << std::endl; // Output: 24


在访问和修改资源时,我们应该遵循以下几个原则:

  • 避免使用裸指针:使用裸指针可能会导致内存泄漏和悬挂指针等常见的编程错误。在使用std::unique_ptr时,应该尽可能避免使用裸指针,以提高代码的安全性和可维护性。

  • 使用get函数获取裸指针:如果需要将std::unique_ptr对象传递给需要裸指针的接口或者函数,可以使用get函数获取裸指针。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void foo(int* ptr) {
    std::cout << *ptr << std::endl;
    }

    int main() {
    std::unique_ptr<int> uptr(new int(42));
    foo(uptr.get()); // Output: 42
    return 0;
    }

    在这个例子中,我们定义了一个接受裸指针的函数foo,并将std::unique_ptr对象的裸指针作为参数传递给它。

  • 使用release函数释放所有权:如果需要将std::unique_ptr对象转移给其他对象或者接口,可以使用release函数释放所有权,并返回裸指针。注意,释放所有权后,std::unique_ptr对象将不再拥有对资源的所有权,我们需要手动释放资源或者将资源交给其他std::unique_ptr对象来管理。例如:

    1
    2
    3
    4
    5
    std::unique_ptr<int> uptr(new int(42));
    int* ptr = uptr.release();
    // do something with ptr
    delete ptr; // manually release the resource

    在使用std::unique_ptr时,我们应该注意以下几个注意事项:

  • 不要使用std::unique_ptr管理数组:std::unique_ptr不适用于管理数组类型的资源,因为它没有提供数组类型的删除器(deleter)。如果需要管理数组类型的资源,可以使用std::unique_ptr的特化版本std::unique_ptr,或者使用std::vector等标准容器来管理。

  • 使用删除器(deleter)自定义资源释放方式:如果需要自定义资源的释放方式,可以使用删除器(deleter)。删除器是一个函数对象或者函数指针,它接受一个指向资源的指针,并负责释放这个资源。在创建std::unique_ptr对象时,可以将删除器作为第二个参数传递给它。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void my_deleter(int* ptr) {
    std::cout << "deleting " << *ptr << std::endl;
    delete ptr;
    }

    int main() {
    std::unique_ptr<int, void(*)(int*)> uptr(new int(42), my_deleter);
    return 0;
    }

    在这个例子中,我们定义了一个删除器my_deleter,并将它作为第二个参数传递给std::unique_ptr对象。在删除std::unique_ptr对象时,删除器将被调用来释放资源。
  • 使用std::make_unique创建std::unique_ptr对象:std::make_unique是C++14中引入的函数模板,它可以方便地创建std::unique_ptr对象,同时避免了使用new操作符来手动分配资源的繁琐过程。例如:
    1
    2
    std::unique_ptr<int> uptr = std::make_unique<int>(42);

    在这个例子中,我们使用std::make_unique函数创建了一个std::unique_ptr对象,并将它初始化为42。

    std::unique_ptr的高级用法

    除了基本用法之外,std::unique_ptr还提供了一些高级用法,例如:

    std::unique_ptr的移动语义

    std::unique_ptr是一个移动语义类型,它可以被移动但不能被复制。在移动std::unique_ptr对象时,资源的所有权将被转移给新的std::unique_ptr对象,原始的std::unique_ptr对象将不再拥有对资源的所有权。例如:
    1
    2
    3
    4
    std::unique_ptr<int> uptr1(new int(42));
    std::unique_ptr<int> uptr2 = std::move(uptr1);
    std::cout << *uptr2 << std::endl; // Output: 42

    在这个例子中,我们创建了一个std::unique_ptr对象uptr1,并将它的所有权移动给了另一个std::unique_ptr对象uptr2。在移动完成后,uptr1将不再拥有对资源的所有权。

    std::unique_ptr的自定义删除器类型

    std::unique_ptr的删除器类型不一定是一个函数指针或者函数对象,它还可以是一个自定义类型。在定义删除器类型时,需要定义一个调用运算符operator(),并将它用于释放资源。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class my_deleter {
    public:
    void operator()(int* ptr) const {
    std::cout << "deleting " << *ptr << std::endl;
    delete ptr;
    }
    };

    int main() {
    std::unique_ptr<int, my_deleter> uptr(new int(42));
    return 0;
    }

    在这个例子中,我们定义了一个自定义删除器类型my_deleter,它包含一个调用运算符,用于释放资源。在创建std::unique_ptr对象时,我们将my_deleter类型作为第二个模板参数传递给它。

    std::unique_ptr的空指针检查

    std::unique_ptr提供了一个成员函数get,可以获取它所管理的资源的裸指针。当std::unique_ptr对象为空指针时,get函数将返回nullptr。我们可以使用bool运算符或者显式的空指针检查来判断std::unique_ptr对象是否为空指针。例如:
    1
    2
    3
    4
    5
    std::unique_ptr<int> uptr;
    if (!uptr) {
    std::cout << "uptr is a null pointer" << std::endl;
    }

    在这个例子中,我们定义了一个空的std::unique_ptr对象uptr,并使用bool运算符来判断它是否为空指针。

总的来说,std::unique_ptr是一种非常方便和安全的资源管理方式,可以避免手动进行内存管理和释放,同时也可以防止内存泄漏和悬挂指针等常见的编程错误。在使用std::unique_ptr时,我们应该遵循基本用法和注意事项,并根据需要使用高级用法来提高代码的可读性和可维护性。

下面是一些示例代码,用于演示std::unique_ptr的基本用法和高级用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <memory>

class MyClass {
public:
MyClass() {
std::cout << "MyClass constructor" << std::endl;
}

~MyClass() {
std::cout << "MyClass destructor" << std::endl;
}

void foo() {
std::cout << "MyClass foo" << std::endl;
}
};

void my_deleter(int* ptr) {
std::cout << "deleting " << *ptr << std::endl;
delete ptr;
}

class my_deleter {
public:
void operator()(int* ptr) const {
std::cout << "deleting " << *ptr << std::endl;
delete ptr;
}
};

int main() {
// Basic usage
std::unique_ptr<int> uptr(new int(42));
std::cout << *uptr << std::endl;
*uptr = 24;
std::cout << *uptr << std::endl;

// Get raw pointer
void* ptr = uptr.get();
std::cout << *static_cast<int*>(ptr) << std::endl;

// Custom deleter
std::unique_ptr<int, void(*)(int*)> uptr2(new int(42), my_deleter);
std::unique_ptr<int, my_deleter> uptr3(new int(42));

// Move semantics
std::unique_ptr<int> uptr4 = std::move(uptr);
std::cout << *uptr4 << std::endl;

// Null pointer check
std::unique_ptr<int> uptr5;
if (!uptr5) {
std::cout << "uptr5 is a null pointer" << std::endl;
}

// Custom class
std::unique_ptr<MyClass> uptr6(new MyClass());
uptr6->foo();

return 0;
}


在这个示例代码中,我们演示了std::unique_ptr的基本用法和高级用法,包括创建std::unique_ptr对象、获取裸指针、使用自定义删除器、使用移动语义、空指针检查、自定义类等。在使用std::unique_ptr时,我们应该根据需要选择合适的用法,以提高代码的安全性和可维护性。

补充一些示例代码,用于演示std::unique_ptr的更多用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <memory>

class MyClass {
public:
MyClass() {
std::cout << "MyClass constructor" << std::endl;
}

~MyClass() {
std::cout << "MyClass destructor" << std::endl;
}

void foo() {
std::cout << "MyClass foo" << std::endl;
}
};

class MyDeleter {
public:
void operator()(MyClass* ptr) const {
std::cout << "MyDeleter deleting MyClass" << std::endl;
delete ptr;
}
};

int main() {
// Create a unique_ptr that owns a dynamically-allocated int
std::unique_ptr<int> uptr(new int(42));
std::cout << *uptr << std::endl; // Output: 42

// Use make_unique to create a unique_ptr
auto uptr2 = std::make_unique<int>(10);
std::cout << *uptr2 << std::endl; // Output: 10

// Release the ownership of the pointer and return its value
int* raw_ptr = uptr.release();
std::cout << *raw_ptr << std::endl; // Output: 42

// Transfer the ownership of a pointer to another unique_ptr
std::unique_ptr<int> uptr3(raw_ptr);
std::cout << *uptr3 << std::endl; // Output: 42

// Custom deleter
std::unique_ptr<MyClass, MyDeleter> uptr4(new MyClass(), MyDeleter());
uptr4->foo(); // Output: MyClass foo

// Access the managed pointer using get()
int* raw_ptr2 = uptr.get();
if (raw_ptr2 != nullptr) {
std::cout << *raw_ptr2 << std::endl; // Output: 42
}

// Null pointer check
std::unique_ptr<int> uptr5;
if (uptr5 == nullptr) {
std::cout << "uptr5 is a null pointer" << std::endl;
}

// Array management using a unique_ptr
std::unique_ptr<int[]> uptr6(new int[3]{1, 2, 3});
std::cout << uptr6[0] << std::endl; // Output: 1

return 0;
}


在这个示例代码中,我们进一步演示了std::unique_ptr的用法,包括使用make_unique创建std::unique_ptr对象、释放所有权、转移所有权、使用自定义删除器、使用get()函数获取裸指针、空指针检查以及管理数组类型的资源。这些示例代码可以帮助开发者更好地理解std::unique_ptr的使用方法和注意事项,并应用到实际的项目开发中。

以下是一些额外的示例代码,演示了std::unique_ptr的更多高级用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <memory>

class MyClass {
public:
MyClass() {
std::cout << "MyClass constructor" << std::endl;
}

~MyClass() {
std::cout << "MyClass destructor" << std::endl;
}

void foo() {
std::cout << "MyClass foo" << std::endl;
}
};

class MyDeleter {
public:
void operator()(MyClass* ptr) const {
std::cout << "MyDeleter deleting MyClass" << std::endl;
delete ptr;
}
};

int main() {
// Custom deleter with lambda function
auto deleter = [](int* ptr) {
std::cout << "deleting " << *ptr << std::endl;
delete ptr;
};
std::unique_ptr<int, decltype(deleter)> uptr(new int(42), deleter);
std::cout << *uptr << std::endl; // Output: 42

// Custom deleter with lambda function capturing an external variable
int factor = 10;
auto deleter2 = [factor](int* ptr) {
std::cout << "deleting " << *ptr << " times " << factor << std::endl;
delete ptr;
};
std::unique_ptr<int, decltype(deleter2)> uptr2(new int(42), deleter2);
std::cout << *uptr2 << std::endl; // Output: 42

// Custom deleter with member function pointer
std::unique_ptr<MyClass, void(MyClass::*)()> uptr3(new MyClass(), &MyClass::foo);
uptr3->foo(); // Output: MyClass foo

// Make unique array
auto uptr4 = std::make_unique<int[]>(3);
uptr4[0] = 1;
uptr4[1] = 2;
uptr4[2] = 3;
std::cout << uptr4[1] << std::endl; // Output: 2

// Get raw pointer and release ownership
int* raw_ptr = uptr4.release();
delete[] raw_ptr;

return 0;
}


在这个示例代码中,我们进一步演示了std::unique_ptr的高级用法,包括使用lambda函数作为删除器、使用成员函数指针作为删除器、使用std::make_unique创建数组类型的std::unique_ptr对象、使用release函数释放所有权。这些示例代码可以帮助开发者更好地理解std::unique_ptr的高级用法,并且可以应用到更加复杂的项目开发中。

接下来是一些示例代码,演示了如何在STL算法中使用std::unique_ptr:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <memory>
#include <algorithm>
#include <vector>

int main() {
// Sort a vector of unique_ptr
std::vector<std::unique_ptr<int>> vec;
vec.push_back(std::make_unique<int>(3));
vec.push_back(std::make_unique<int>(1));
vec.push_back(std::make_unique<int>(2));
std::sort(vec.begin(), vec.end(), [](const std::unique_ptr<int>& a, const std::unique_ptr<int>& b) {
return *a < *b;
});
for (const auto& ptr : vec) {
std::cout << *ptr << std::endl; // Output: 1 2 3
}

// Transform a vector of int into a vector of unique_ptr
std::vector<int> vec2 {1, 2, 3};
std::vector<std::unique_ptr<int>> vec3;
std::transform(vec2.begin(), vec2.end(), std::back_inserter(vec3), [](int i) {
return std::make_unique<int>(i);
});
for (const auto& ptr : vec3) {
std::cout << *ptr << std::endl; // Output: 1 2 3
}

// Remove elements from a vector of unique_ptr
std::vector<std::unique_ptr<int>> vec4;
vec4.push_back(std::make_unique<int>(1));
vec4.push_back(std::make_unique<int>(2));
vec4.push_back(std::make_unique<int>(3));
auto new_end = std::remove_if(vec4.begin(), vec4.end(), [](const std::unique_ptr<int>& ptr) {
return *ptr % 2 == 0;
});
vec4.erase(new_end, vec4.end());
for (const auto& ptr : vec4) {
std::cout << *ptr << std::endl; // Output: 1 3
}

return 0;
}


在这个示例代码中,我们演示了如何在STL算法中使用std::unique_ptr,包括对std::vector中的std::unique_ptr进行排序、将std::vector转换为std::vector>、从std::vector>中删除指定的元素等。使用std::unique_ptr作为STL算法的元素类型,可以更加方便和安全地管理资源,同时也可以避免内存泄漏和悬挂指针等问题。

以下是一些示例代码,演示了如何使用std::unique_ptr实现RAII的资源管理模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <memory>

class MyResource {
public:
MyResource() {
std::cout << "MyResource constructor" << std::endl;
}

~MyResource() {
std::cout << "MyResource destructor" << std::endl;
}

void foo() {
std::cout << "MyResource foo" << std::endl;
}
};

class MyManager {
public:
MyManager() {
std::cout << "MyManager constructor" << std::endl;
m_resource = std::make_unique<MyResource>();
}

~MyManager() {
std::cout << "MyManager destructor" << std::endl;
}

void use_resource() {
m_resource->foo();
}

private:
std::unique_ptr<MyResource> m_resource;
};

int main() {
// RAII with unique_ptr
{
std::unique_ptr<MyResource> uptr(new MyResource());
uptr->foo();
}

// RAII with a manager class
{
MyManager manager;
manager.use_resource();
}

return 0;
}


在这个示例代码中,我们演示了如何使用std::unique_ptr实现RAII的资源管理模式。在第一个示例中,我们创建了一个std::unique_ptr对象,使用它来管理MyResource类型的资源,并在作用域结束时自动释放资源。在第二个示例中,我们使用一个管理类MyManager来封装MyResource类型的资源,并使用std::unique_ptr来管理MyResource对象的所有权。在MyManager的构造函数中创建MyResource对象,并在析构函数中释放MyResource对象的资源,同时提供一个接口use_resource,用于使用MyResource对象的资源。这种基于std::unique_ptr的RAII模式可以避免手动进行内存管理和释放,从而提高代码的可读性和可维护性。

以下是一些示例代码,演示了如何使用std::unique_ptr作为返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <memory>

class MyClass {
public:
MyClass() {
std::cout << "MyClass constructor" << std::endl;
}

~MyClass() {
std::cout << "MyClass destructor" << std::endl;
}

void foo() {
std::cout << "MyClass foo" << std::endl;
}
};

std::unique_ptr<MyClass> create_my_class() {
return std::make_unique<MyClass>();
}

std::unique_ptr<MyClass[]> create_my_class_array(size_t size) {
return std::make_unique<MyClass[]>(size);
}

int main() {
// Return a unique_ptr from a function
auto uptr = create_my_class();
uptr->foo(); // Output: MyClass foo

// Return a unique_ptr array from a function
auto uptr_array = create_my_class_array(3);
uptr_array[0].foo(); // Output: MyClass foo

return 0;
}


在这个示例代码中,我们演示了如何使用std::unique_ptr作为函数的返回值类型,包括返回单个对象和返回对象数组。通过使用std::unique_ptr作为返回值类型,我们可以避免内存泄漏和悬挂指针等问题,同时也可以方便地传递对象的所有权,使代码更加清晰和安全。

以下是一些示例代码,演示了如何使用std::unique_ptr实现Pimpl Idiom(Pointer to Implementation Idiom):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <memory>

class MyClassImpl {
public:
void foo() {
std::cout << "MyClassImpl foo" << std::endl;
}
};

class MyClass {
public:
MyClass() : m_impl(std::make_unique<MyClassImpl>()) {}

void foo() {
m_impl->foo();
}

private:
std::unique_ptr<MyClassImpl> m_impl;
};

int main() {
MyClass obj;
obj.foo(); // Output: MyClassImpl foo

return 0;
}


在这个示例代码中,我们演示了如何使用std::unique_ptr实现Pimpl Idiom(Pointer to Implementation Idiom)。Pimpl Idiom是一种设计模式,它的核心思想是将类的实现细节和接口分离,使得实现细节可以被封装在一个私有类中,并通过一个指针来访问这个私有类,从而避免将实现细节暴露在类的接口中。在这个示例中,我们将MyClass的实现细节封装在MyClassImpl中,并通过一个std::unique_ptr指针来访问MyClassImpl,从而实现了Pimpl Idiom。使用Pimpl Idiom可以提高代码的可维护性和可重用性,同时也可以减少编译时间和链接时间。

以下是一些示例代码,演示了如何使用std::unique_ptr实现基于策略模式的设计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <memory>

class Strategy {
public:
virtual void execute() = 0;
};

class ConcreteStrategyA : public Strategy {
public:
void execute() override {
std::cout << "Executing ConcreteStrategyA" << std::endl;
}
};

class ConcreteStrategyB : public Strategy {
public:
void execute() override {
std::cout << "Executing ConcreteStrategyB" << std::endl;
}
};

class Context {
public:
Context(std::unique_ptr<Strategy> strategy) : m_strategy(std::move(strategy)) {}

void set_strategy(std::unique_ptr<Strategy> strategy) {
m_strategy = std::move(strategy);
}

void execute_strategy() {
m_strategy->execute();
}

private:
std::unique_ptr<Strategy> m_strategy;
};

int main() {
auto strategyA = std::make_unique<ConcreteStrategyA>();
auto strategyB = std::make_unique<ConcreteStrategyB>();

Context context(std::move(strategyA));
context.execute_strategy(); // Output: Executing ConcreteStrategyA

context.set_strategy(std::move(strategyB));
context.execute_strategy(); // Output: Executing ConcreteStrategyB

return 0;
}


在这个示例代码中,我们演示了如何使用std::unique_ptr实现基于策略模式的设计。策略模式是一种设计模式,它的核心思想是将算法的实现细节和客户端分离,从而使得算法的实现可以独立于客户端进行修改和重用。在这个示例中,我们使用了一个Context类来封装策略的执行,使用std::unique_ptr来管理Strategy对象的所有权,并使用set_strategy函数来动态切换不同的策略实现。这种基于std::unique_ptr的策略模式可以提高代码的可维护性和可扩展性,同时也可以避免内存泄漏和悬挂指针等问题。

以下是一些示例代码,演示了如何使用std::unique_ptr实现移动语义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <memory>

class MyClass {
public:
MyClass() {
std::cout << "MyClass constructor" << std::endl;
}

~MyClass() {
std::cout << "MyClass destructor" << std::endl;
}

MyClass(const MyClass&) {
std::cout << "MyClass copy constructor" << std::endl;
}

MyClass& operator=(const MyClass&) {
std::cout << "MyClass copy assignment operator" << std::endl;
return *this;
}

MyClass(MyClass&&) noexcept {
std::cout << "MyClass move constructor" << std::endl;
}

MyClass& operator=(MyClass&&) noexcept {
std::cout << "MyClass move assignment operator" << std::endl;
return *this;
}
};

int main() {
// Move semantics with unique_ptr
auto uptr1 = std::make_unique<MyClass>();
auto uptr2 = std::move(uptr1);
auto uptr3 = std::make_unique<MyClass>();
uptr3 = std::move(uptr2);

return 0;
}


在这个示例代码中,我们演示了如何使用std::unique_ptr实现移动语义。移动语义是一种C++11引入的新特性,它允许我们将资源从一个对象转移到另一个对象,而不需要进行资源的复制和销毁,从而提高程序的性能和效率。在这个示例中,我们使用std::unique_ptr来管理MyClass类型的资源,使用std::move函数来实现资源的移动,从而调用MyClass的移动构造函数和移动赋值运算符。使用std::unique_ptr和移动语义可以避免资源的不必要复制和销毁,从而提高程序的性能和效率。


详细介绍std::unique_ptr
https://qiangsun89.github.io/2023/04/24/详细介绍std-unique-ptr/
作者
Qiang Sun
发布于
2023年4月24日
许可协议