C++在使用PImpl技术时,template/typename的不常见用法

Posted 烧饼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++在使用PImpl技术时,template/typename的不常见用法相关的知识,希望对你有一定的参考价值。

PImpl:Pointer to implementation,常用于隐藏实现细节,构造拥有稳定 ABI 的 C++ 库接口,及减少编译时依赖。

在使用pimpl技术时,pimpl是类静态变量,对其在源文件中的实现需要使用typename关键字

对于模板类的静态成员变量的定义,你需要使用完整的模板类型限定符来指定 std::unique_ptr 的类型。在这个示例中,我们使用 typename MyClass<T>::Impl 来指定 std::unique_ptr 的类型。

// MyClass.h

#include <memory>

template<typename T>
class MyClass 
public:
    void doSomething();

private:
    class Impl;
    static std::unique_ptr<Impl> p_impl;  // 静态 p_impl 变量的声明
;

// MyClass.cpp

template<typename T>
class MyClass<T>::Impl 
public:
    void doSomething() 
        // 实现具体逻辑
        std::cout << "Doing something..." << std::endl;
    
;

template<typename T>
std::unique_ptr<typename MyClass<T>::Impl> MyClass<T>::p_impl = std::make_unique<typename MyClass<T>::Impl>();  // 静态 p_impl 变量的定义

template<typename T>
void MyClass<T>::doSomething() 
    p_impl->doSomething();

如果一个模板类使用p_impl技术,同时内部包含模板函数,那么模板函数的调用需要额外使用template关键字

在最后的代码p_impl->template doSomething<U>();处,使用template关键字告诉编译器我们正在引用一个模板成员函数doSomething<U>()。不使用template关键字会报错

// MyClass.h

#include <memory>

template<typename T>
class MyClass 
public:
    MyClass();
    template<typename U>
    void doSomething();

private:
    class Impl;
    std::unique_ptr<Impl> p_impl;
;

// MyClass.cpp

template<typename T>
class MyClass<T>::Impl 
public:
    template<typename U>
    void doSomething() 
        // 实现具体逻辑
        std::cout << "Doing something with type " << typeid(U).name() << "..." << std::endl;
    
;

template<typename T>
MyClass<T>::MyClass() : p_impl(std::make_unique<Impl>()) 

template<typename T>
template<typename U>
void MyClass<T>::doSomething() 
    p_impl->template doSomething<U>();

何时在 C++ 中的嵌套类上使用 Pimpl 模式,反之亦然?

【中文标题】何时在 C++ 中的嵌套类上使用 Pimpl 模式,反之亦然?【英文标题】:When to use Pimpl pattern over Nested class in C++ or vice versa? 【发布时间】:2019-08-01 10:05:23 【问题描述】:

在 C++ 中,大多数开发人员都使用 pimpl idiomopaque pointers 对公共 API 隐藏私有数据/实现,例如:

    => 第一种情况 ## 不透明指针和 PIMPL 成语 ##

//在头文件中

class Node; 
class Graph   
 public:    
  A();  
 private:  
  Node* m_nodeImpl; 

// 类节点将在各自的cpp中定义

2。 =>第二种情况##内部类/嵌套类方法##

//在头文件中

class Graph   
 public:    
  Graph();  
 private:  
  class Node
  
    // implementation goes here
  
  Node* m_nodeImpl;


问题是...

    从类设计的角度来看,这两者之间的实际区别是什么(可能是设计模式)? 每种方法的优缺点是什么?

【问题讨论】:

您可以将两者结合起来,您只需声明 Graph::Node 类,并在源文件中定义。 至于区别,在第二个示例中,人类阅读头文件可以看到Node类的详细信息。 @Someprogrammerdude 我认为这是 pimpl/opaque_ptr 相对于嵌套类 impl 的主要优势,对吧? (甚至隐藏细节) 通常这不是“隐藏”人类的问题,而是依赖问题。 IE。如果Node 的实现(和声明)取决于其他一些类,则需要将其包含在标题中。在 PIML 情况下,您不需要这个。这对于例如不需要公开实现细节的 API 类来说可能很重要。 @sklott 是的,我忘记了这一点,感谢您再次引起我的注意,是的,因此考虑到这两个因素,pimpl 是首选,对吧?那么实际上何时在 C++ 中使用内部类?除了人类可读的东西 【参考方案1】:

你混淆了几件事:

    第一个例子

      Type: opaque - 这意味着类型 name 对该标头的用户可见,但定义是隐藏的。

      当您想与用户共享指针时,不透明类型特别有用,而不是它指向 的详细信息(或者阻止用户摆弄它,或者打破依赖关系)。

      存储:粉刺。这只是意味着用户知道您(可能)在某处有一个 opaque 类型的实例,并且他们知道您的***对象的大小包括该指针。

    第二个例子

      类型:嵌套 - 这样可以避免污染封闭的命名空间(因此在您的程序的同一命名空间中可以有其他类型的 Node,与第一个示例不同)但会暴露所有细节。

      请注意,您的嵌套类型也可以前向声明,以控制可见性和命名空间污染。

      存储:丢失。你是不是也想在这里有一个Node *m_impl?否则,这些示例无法直接进行比较,因为您的 Graph 无处可保留 Node,但是声明了类型。

【讨论】:

感谢您的简短而清晰的描述,Totally forgot to add Node impl ptr on 2nd example,我明白了您的意思。

以上是关于C++在使用PImpl技术时,template/typename的不常见用法的主要内容,如果未能解决你的问题,请参考以下文章

何时在 C++ 中的嵌套类上使用 Pimpl 模式,反之亦然?

指向 C++ 中类的指针的 PIMPL 习语

如何在 C++ 中获取抽象(?)pimpl 的调试信息?

C++: The PIMPL idiom

C++ 设计篇之——pimpl 机制

pimpl idiom