pimpl 习惯用法是否比始终使用 unique_ptr 作为成员变量更好?

如何解决pimpl 习惯用法是否比始终使用 unique_ptr 作为成员变量更好?

在我的工作场所,我们有这样的约定:几乎每个类(除了极少数例外)都使用 unique_ptr、原始指针或引用作为成员变量来实现。

这是因为编译时间的原因:通过这种方式,您只需要在头文件中对类进行前向声明,并且只需要在 cpp 中包含该文件。此外,如果您更改包含在 unique_ptr 中的类的 .h 或 .cpp,则无需重新编译。

我认为这种模式至少有以下缺点:

  • 它迫使您编写自己的复制构造函数和赋值运算符,并且您必须单独管理每个变量,如果您想保持复制的语义。
  • 代码的语法变得非常繁琐,例如您将使用 std::vector<std::unique_ptr<MyClass>> 而不是更简单的 std::vector<MyPimplClass>
  • 指针的常量性不会传播到指向的对象,除非你使用 std::experimental::propagate_const,我不能使用。

因此,我想到建议对作为指针包含的类使用 pImpl 习惯用法,而不是在任何地方都使用指针。通过这种方式,我认为我们可以两全其美:

  • 更快的编译时间:pimpl 减少了编译依赖
  • 要编写复制构造函数和复制赋值运算符,您只需执行以下操作:
A::A(const A& rhs) : pImpl(std::make_unique<Impl>(*rhs.pImpl)) {}
A& A::operator=(const A& rhs) {
  *pImpl = *rhs.pImpl;
  return *this;
}
  • 常量被传播到成员对象。

此时我与我的同事进行了讨论,他们认为 pImpl 并不比在任何地方使用指针更好,原因如下:

  • 与使用指针相比,它减少了编译依赖性,因为如果您使用 pImpl,则在更改公共接口时必须重新编译包含 pImpl 类的类:如果您只使用指针而不是您赢得的 pImpl 类'即使改变头文件也不需要重新编译。

现在有点糊涂了。我认为我们的实际约定并不比 pImpl 好,但我无法争论为什么。

所以我有一些问题:

  • 在这种情况下,pImpl 习语是一个不错的选择吗?
  • 除了我提到的那些之外,我们正在使用的模式还有其他缺点吗?

编辑: 我正在添加一些示例来阐明这两种方法

  • unique_ptr 作为成员的方法
// B.h
#pragma once
class B {
    int i = 42;
public:
    void print();
};

// B.cpp
#include "B.h"
#include <iostream>
void B::print() { std::cout << i << '\n'; }

// A.h
#pragma once
#include <memory>
class B;
class A {
    std::unique_ptr<B> b;
public:
    A();
    ~A();
    void printB();
};

// A.cpp
#include "A.h"
#include "B.h"
A::A() : b{ std::make_unique<B>() } {}
A::~A() = default;
void A::printB() {  b->print(); }
// Bp.h
#pragma once
#include <memory>
class Bp {
    struct Impl;
    std::unique_ptr<Impl> m_pImpl;
public:
    Bp();
    ~Bp();
    void print();
};

// Bp.cpp
#include "Bp.h"
#include <iostream>
struct Bp::Impl {
    int i = 42;
};
Bp::Bp() : m_pImpl{ std::make_unique<Impl>() } {}
Bp::~Bp() = default;
void Bp::print() {  std::cout << m_pImpl->i << '\n'; }

// Ap.h
#pragma once
#include <memory>
#include "Bp.h"
class Ap {
    Bp b;
public:
    void printB();
};

// Ap.cpp
#include "Ap.h"
#include "Bp.h"
void Ap::printB() { b.print(); }

主要内容

// main.cpp
#include "Ap.h"
#include "A.h"

int main(int argc,char** argv) {
    A a{};
    a.printB();

    Ap aPimpl{};
    aPimpl.printB();
}

此外,当我说第一种方法不需要重新编译时,我想更准确地说,这是不准确的。确实,我们需要重新编译的文件更少:

  • 如果我们更改 B.h,我们只需要重新编译 A.cpp、B.cpp。
  • 如果我们改变Bp.h,我们需要重新编译Ap.cpp、Bp.cpp和main.cpp

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?