是否可能在c ++中编写一个敏捷Pimpl? [英] Is it possible to write an agile Pimpl in c++?

查看:156
本文介绍了是否可能在c ++中编写一个敏捷Pimpl?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在玩Pimpl的成语,并从中获得各种好处。我唯一没有太热衷的是我在定义函数时得到的感觉。




  • 一旦在标题中(P def)

  • 一次在.cpp Impl def)

  • 一旦处于.cpp(Impl Impl)中间

  • 一旦位于.cpp(P Impl)



我真的很喜欢减少代码差异和冗余,我觉得我的代码不够好,在我目前的项目中甚至相对复杂的Impls中更改函数。



我的问题是,有什么有效的方式暗示或模板我的类,定义一个新的函数,我只需要编写一个显式的定义和实现,并使其余的空间保持接近代码中的解释;如果我要改变一个功能,那么所需的改变将尽可能少?

解决方案

让我们假设你的标题开头是这样:

  class X 
{
public:
... la de dah ...
private:
struct Impl;
Impl * p_impl_;
};

然后当你添加函数时,你可以选择:


$成员函数定义实现了逻辑,引用 p_impl_


  • return p_impl-> same_fn(all_the_args) ; 并保持 Impl 类中的逻辑?


  • 如果你选择1.那么你最终在头中有一个函数声明,并在匹配的实现文件中定义一个(比平时稍微更乱)。



    如果你选择2.那么你最终在头文件中的函数声明,在匹配的实现文件中的包装/转发定义,并且至少在 Impl 结构(我不倾向于定义 Impl 类定义之外的函数 - 这是一个实现细节,接口不是公共的)。



    没有一个理想的方法来改善这种情况(即在你的构建过程中,宏黑客和额外的代码生成脚本可能偶尔被保证,但很少)。






    不管整个堆,尽管可能感兴趣的是,第二种方法的变体是首先实现不使用pimpl成语(具有适当的标题和可选的内联函数)的类, ,然后可以使用pimpl管理对象将其 包装 并转发函数,并且以这种方式保持有某些代码在某些地方决定使用功能而不使用pimpl包装器,也许为了提高性能/减少内存使用以重编译依赖为代价。您还可以使用模板的特定实例化,而不公开模板的代码。



    为了说明此选项(如注释中所要求),让我们开始在其自己的文件中使用非傻的非pimpl 类X ,然后创建一个 Pimpl :: X 的命名空间和相同的类名是完全可选的,但有利于翻转客户端代码使用,并提醒 - 这不意味着简洁,这里的观点是让非pImpl版本也可用) p>

      // xh 
    class X
    {
    public:
    int get const {return n_; } // inline
    void operator =(int); // out-of-line definition
    private:
    int n_;
    };

    // x.c ++
    #include< x.h>
    void X :: operator =(int n){n_ = n * 2; }

    // x_pimpl.h
    命名空间Pimpl
    {
    class X
    {
    public:
    X
    X(const X&);
    〜X();
    X& operator =(const X&);
    int get()const;
    void operator =(int);
    private:
    struct Impl;
    Impl * p_impl_;
    };
    }

    x_pimpl.c ++
    #include< x.h>
    命名空间Pimpl
    {
    struct X :: Impl
    {
    :: X x_;
    };

    //通常处理...
    X():p_impl_(new Impl){}
    X(const X& rhs):p_impl(new Impl){p_impl_ - > x_ = rhs.p_impl _-> x_; }
    〜X(){delete p_impl_; }
    X& operator =(const X& rhs){p_impl _-> x_ = rhs.p_impl _-> x_; return * this; }

    //换行...
    int X :: get()const {return p_impl _-> x_.get(); }
    void X :: operator =(int n){p_impl _-> x_ = n; }
    }



    如果您选择上述2的变体, 一个可用的实体在它自己的权利,然后是 - 你可能最终有2个声明和2个定义与单个函数相关,但然后其中一个定义将是一个简单的包装/转发功能,只有重大和繁琐,如果这些功能非常短暂且众多,但有很多参数。


    I've been playing with the Pimpl idiom and reaping all sorts of benefits from it. The only thing I haven't been too keen on is the feeling I get when I define the functions.

    • Once in the header (P def)
    • Once at the top of the .cpp (Impl def)
    • Once in the middle of the .cpp (Impl Impl)
    • Once at the lower end of the .cpp (P Impl)

    I really enjoy cutting down code disparity and redundancy, and I feel like my code is less than well oiled when I have to add or change functions in even relatively complex Impls in my current project.

    My question is, what effective ways are there to imply or template my classes in such a way that if I were to define a new function, I'd only have to write one explicit definition and implementation, and have the rest remain spatially close to the explicits in code; and if I were to change a function, the changes necessary would be as few as possible?

    解决方案

    Let's postulate your header starts something like this:

    class X
    {
      public:
        ...la de dah...
      private:
        struct Impl;
        Impl* p_impl_;
    };
    

    Then when you add functions you have a choice to make:

    1. do you have the X member function definition implement the logic, referring to p_impl_-> things all over the place, or

    2. return p_impl->same_fn(all_the_args); and keep the logic inside the Impl class?

    If you choose 1. then you end up with a function declaration in the header, and a (slightly messier than usual) definition in the matching implementation file.

    If you choose 2. then you end up with a function declaration in the header file, a wrapping/forwarding definition in the matching implementation file, and at a minimum a definition in the Impl structure (I tend not to define the functions outside the Impl class definition - it's an implementation detail and the interface is not public anyway).

    There is no generally desirable way to improve on this situation (i.e. macro hackery and extra code-generation scripts in your build process may occasionally be warranted, but very rarely).


    It may not matter a whole heap, though it may be of interest that a variation on the second approach is to first implement a class that doesn't use the pimpl idiom (complete with proper header and optionally inline functions), you can then wrap it with a pimpl management object and forward functions to it, and in that way you keep the freedom to have some code somewhere some day decide it wants to use the functionality without using the pimpl wrapper, perhaps for improved performance / reduced memory usage at the cost of the recompilation dependency. You can also do this to make use of a specific instantiation of a template without exposing the template's code.

    To illustrate this option (as requested in a comment), let's start with a silly non-pimpl class X in its own files, then create a Pimpl::X wrapper (the use of namespace and the same class name is entirely optional but facilitates flipping client code to use either, and a reminder - this isn't meant to be concise, the point here is to let a non-pImpl version be usable too):

    // x.h
    class X
    {
      public:
        int get() const { return n_; }   // inline
        void operator=(int);  // out-of-line definition
      private:
        int n_;
    };
    
    // x.c++
    #include <x.h>
    void X::operator=(int n) { n_ = n * 2; }
    
    // x_pimpl.h
    namespace Pimpl
    {
        class X
        {
          public:
            X();
            X(const X&);
            ~X();
            X& operator=(const X&);
            int get() const;
            void operator=(int);
          private:
            struct Impl;
            Impl* p_impl_;
        };
    }
    
    x_pimpl.c++
    #include <x.h>
    namespace Pimpl
    {
        struct X::Impl
        {
            ::X x_; 
        };
    
        // the usual handling...
        X() : p_impl_(new Impl) { }
        X(const X& rhs) : p_impl(new Impl) { p_impl_->x_ = rhs.p_impl_->x_; }
        ~X() { delete p_impl_; }
        X& operator=(const X& rhs) { p_impl_->x_ = rhs.p_impl_->x_; return *this; }
    
        // the wrapping...
        int X::get() const { return p_impl_->x_.get(); }
        void X::operator=(int n) { p_impl_->x_ = n; }
    }
    

    If you opt for the above variation on 2, which makes the "implementation" a usable entity in it's own right, then yes - you may end up with 2 declarations and 2 definitions related to a single function, but then one of the definitions will be a simple wrapper/forwarding function which is only significantly repetitive and tedious if the functions are very short and numerous but have lots of parameters.

    这篇关于是否可能在c ++中编写一个敏捷Pimpl?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆