C++-DLL-rule five-pimpl(详细教程)

基础知识

当类有自己的资源需要管理时,那么必须重写

  1. 析构函数
    默认的析构函数不会释放资源

  2. 拷贝构造
    默认的拷贝构造仅仅是“浅拷贝”

  3. 拷贝赋值
    默认的拷贝赋值仅仅是“浅拷贝”

有了左值和右值的区分,那么拷贝构造和拷贝赋值需要写两份

CBox(const CBox& other);
CBox(CBox&& other) noexcept;

CBox& operator= (const CBox& other);
CBox& operator= (CBox&& aBox) noexcept;

~CBox();

class rule_of_five
{
    char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block
 public:
    rule_of_five(const char* s = "")
    : cstring(nullptr)
    { 
        if (s) {
            std::size_t n = std::strlen(s) + 1;
            cstring = new char[n];      // allocate
            std::memcpy(cstring, s, n); // populate 
        } 
    }
 
    ~rule_of_five()
    {
        delete[] cstring;  // deallocate
    }
 
    rule_of_five(const rule_of_five& other) // copy constructor
    : rule_of_five(other.cstring)
    {}
 
    rule_of_five(rule_of_five&& other) noexcept // move constructor
    : cstring(std::exchange(other.cstring, nullptr))
    {}
 
    rule_of_five& operator=(const rule_of_five& other) // copy assignment
    {
         return *this = rule_of_five(other);
    }
 
    rule_of_five& operator=(rule_of_five&& other) noexcept // move assignment
    {
        std::swap(cstring, other.cstring);
        return *this;
    }
 
// alternatively, replace both assignment operators with 
//  rule_of_five& operator=(rule_of_five other) noexcept
//  {
//      std::swap(cstring, other.cstring);
//      return *this;
//  }
};

(摘录)

c++,类的对象作为形参时一定会调用复制构造函数吗?
答:如果参数是引用传递,则不会调用任何构造函数;如果是按值传递,则调用复制构造函数,按参数的值构造一个临时对象,这个临时对象仅仅在函数执行是存在,函数执行结束之后调用析构函数。

如果类中没有定义复制构造函数 那对象就不能作为形参?
答:如果没有定义,编译器会自动为你定义一个,编译器自己定义的复制构造函数是按类成员变量的值复制的。有几个成员变量就重新创建一个对象,那个对象的成员变量和被复制的对象其成员变量的值相同。这里如果成员变量有指针的时候,就会出现多个指针指向同一个对象的问题。

unique_ptr删除拷贝语义

#include <iostream>
#include <memory>

using std::cout;
using std::endl;

/*
The first part - copyable/movable relates to the fact that with the simple - raw - pointer we can only shallow copy an object.
 Of course, this happens in every case you have a pointer in your class

 在类里写了一个裸指针,如果用编译器自动生成的拷贝/移动函数,都是浅拷贝。在处理不当的情况下,这个裸指针很可能称为空悬指针

 目前有一种流行的想法:显式写了析构函数,那么其他4个函数都必须要写,要不就一个都不要显式的写出来
 当然了,我个人认为没有必要,完美的事物会带来复杂性,而复杂性导致不可维护,这是一个trade-off问题

 c++11后很少会用到裸指针,一般都是用智能指针,智能指针的话,一般用的是 std::unique_ptr
 类的成员函数用到了 unique_ptr 后,编译器就没有了拷贝构造和拷贝赋值函数了
 1. 如果这是需要的结果,这就是只有移动语义的类,类的对象间不能赋值
 2. 如果这不是需要的结果,那么需要自己手写拷贝函数

写DLL时,一般用到 PIMPL 方法,用智能指针 std::unique_ptr
1. copyable and moveble class
2. noncopyble and movable class

当然也可以用 std::shared_ptr

*/



class Person
{
public:
    Person() = default;

private:
    std::unique_ptr<int>  m_age;

};


int main(void)
{
    Person p1;
    Person p2;
    p1 = p2;
    /*
        copy assignment operator of 'Person' is implicitly deleted because field 'm_age' has a deleted copy assignment operator
    */

    return 0;

}

较好的写法

#include <iostream>
#include <memory>
#include <string>

using std::cout;
using std::endl;


class Person
{
public:
    Person() = default;

    void printMe(std::string aboutme)
    {
        cout << aboutme << endl;

    }

private:
    // 显式删除
    Person(const Person &) = delete;
    Person& operator=(const Person&) = delete;

private:
    std::unique_ptr<int>  m_age;

};



int main(void)
{
    Person p;
    p.printMe("this is a ");

    return 0;

}

unique_ptr增加拷贝语义

#include <iostream>
#include <memory>
#include <string>

using std::cout;
using std::endl;


class Person
{
public:
    explicit Person(int age) : m_age(new int(age))
    {
    }

    ~Person() = default;
    Person(Person &&) noexcept = default;
    Person& operator=(Person &&) noexcept = default;

public:
    Person(const Person &other) : m_age(new int(*other.m_age))
    {
    }

    Person& operator=(const Person& other)
    {
        if (this != &other)
        {
            m_age.reset(new int(*other.m_age));
        }

        return *this;
    }

    void PrintAge()
    {
        cout << "m_age: " << m_age << endl;
        cout << "*m_age: " << *m_age << endl;
    }

private:
    std::unique_ptr<int>  m_age;

};



int main(void)
{
    Person p(10);

    Person p1(100);

    p.PrintAge();
    p1.PrintAge();

    p = p1;
    p.PrintAge();

    return 0;

}


/*
m_age: 0000022C095820B0
*m_age: 10
m_age: 0000022C09581F80
*m_age: 100
m_age: 0000022C09582010
*m_age: 100
*/

PIMPL

使用std::unique_ptr实现,当然也可以使用std::shared_ptr实现

无拷贝语义的PIMPL

  1. 构造的参数传给impl的构造函数
  2. 私有变量全部写在impl中
  3. 要实现的接口全在impl中

.h

#ifndef PERSON_H
#define PERSON_H

#include <memory>


class Person
{
public:
    explicit Person(int age);

public:
// 实现全部在cpp里
    ~Person();
    Person(Person &&) noexcept;
    Person& operator=(Person &&) noexcept;

private:
    // 删除拷贝语义
    Person(const Person&) = delete;
    Person &operator=(const Person&) = delete;


public:
    // 暴露出的接口
    void PrintMe();

private:
    // Impl
    class Impl;
    std::unique_ptr<Impl> m_pimpl;
};

#endif // PERSON_H

.cpp

#include <iostream>
#include <string>

#include "person.h"

using std::cout;
using std::endl;


class Person::Impl
{


public:
    Impl(int age) : m_age(age)
    {

    }

public:
    ~Impl() = default;

public:
    void PrintMe()
    {
        cout << "in Impl " << m_age << endl;
    }

private:
    int m_age;

};


Person::Person(int age) : m_pimpl(new Impl(age))
{

}


void Person::PrintMe()
{
    m_pimpl->PrintMe();
}

Person::~Person()=default;
Person::Person(Person &&other) noexcept = default;
Person& Person::operator=(Person &&other) noexcept = default;

.main

#include "person.h"


int main(void)
{
    Person p(28);

    p.PrintMe();

    return 0;
}

有拷贝语义的PIMPL

.h

#ifndef PERSON_H
#define PERSON_H

#include <memory>


class Person
{
public:
    explicit Person(int age);

public:
// 实现全部在cpp里
    ~Person();
    Person(Person &&) noexcept;
    Person& operator=(Person &&) noexcept;

public:
    // 实现拷贝语义
    Person(const Person&);
    Person &operator=(const Person&);


public:
    // 暴露出的接口
    void PrintMe();

private:
    // Impl
    class Impl;
    std::unique_ptr<Impl> m_pimpl;
};

#endif // PERSON_H

.cpp

#include <iostream>
#include <string>

#include "person.h"

using std::cout;
using std::endl;


class Person::Impl
{


public:
    Impl(int age) : m_age(age)
    {

    }

public:
    ~Impl() = default;

public:
    void PrintMe()
    {
        cout << "in Impl " << m_age << endl;
    }

private:
    int m_age;

};


Person::Person(int age) : m_pimpl(new Impl(age))
{

}

// 实现拷贝语义
Person::Person(const Person& other) : m_pimpl(new Impl(*other.m_pimpl))
{

}


Person& Person::operator=(const Person &other)
{
    if (this != &other)
    {
        m_pimpl.reset(new Impl(*other.m_pimpl));
    }

    return *this;
}


void Person::PrintMe()
{
    m_pimpl->PrintMe();
}


Person::~Person()=default;
Person::Person(Person &&other) noexcept = default;
Person& Person::operator=(Person &&other) noexcept = default;

.main

#include "person.h"


int main(void)
{
    Person p(28);

    p.PrintMe();

    Person p1(50);
    p1.PrintMe();

    p = p1;
    p.PrintMe();  // 50


    return 0;
}

增加const语义

If you declare a method const then you cannot change members of the object.
In other words, they become const. But it’s a problem for our m_pImpl which is a pointer.
In a const method this pointer will also become const which means we cannot assign a different value to it…
but… we can happily call all methods of this underlying private class (not only constant)!.

非const对象,可以调用const函数
const对象,不可以调用非const函数
也就是说当写了一个const 对象后,如果不增加const函数,那么对象很可能无函数可调用了。
当然,现实场景可以是禁止写一个const对象,不负责const对象的函数调用

.h

#ifndef PERSON_H
#define PERSON_H

#include <memory>


class Person
{
public:
    explicit Person(int age);

public:
// 实现全部在cpp里
    ~Person();
    Person(Person &&) noexcept;
    Person& operator=(Person &&) noexcept;

public:
    // 实现拷贝语义
    Person(const Person&);
    Person &operator=(const Person&);


public:
    // 暴露出的接口
    void PrintMe();
    void PrintMe() const;

private:
    // Impl
    class Impl;

    // 实现const语义
    const Impl* Pimpl() const { return m_pimpl.get(); }
    Impl* Pimpl() { return m_pimpl.get(); }

    std::unique_ptr<Impl> m_pimpl;
};

#endif // PERSON_H

.cpp

#include <iostream>
#include <string>

#include "person.h"

using std::cout;
using std::endl;


class Person::Impl
{


public:
    Impl(int age) : m_age(age)
    {

    }

public:
    ~Impl() = default;

public:
    // 实现const 语义
    void PrintMe() const
    {
        cout << "in const in Impl " << m_age << endl;
    }

    void PrintMe()
    {
        cout << "not const in Impl " << m_age << endl;
    }

private:
    int m_age;

};


Person::Person(int age) : m_pimpl(new Impl(age))
{

}

// 实现拷贝语义
Person::Person(const Person& other) : m_pimpl(new Impl(*other.m_pimpl))
{

}


Person& Person::operator=(const Person &other)
{
    if (this != &other)
    {
        m_pimpl.reset(new Impl(*other.m_pimpl));
    }

    return *this;
}


void Person::PrintMe()
{
    Pimpl()->PrintMe();
}

// 实现const语义
void Person::PrintMe() const
{
    Pimpl()->PrintMe();
}


Person::~Person()=default;
Person::Person(Person &&other) noexcept = default;
Person& Person::operator=(Person &&other) noexcept = default;

.main

#include "person.h"


int main(void)
{
    Person p(28);

    p.PrintMe();

    const Person p1(50);
    p1.PrintMe();


    return 0;
}
posted @ 2020-08-22 08:41  duohappy  阅读(288)  评论(0编辑  收藏  举报