【问题标题】:Can a class be private to a translation unit?课程可以是翻译单元私有的吗?
【发布时间】:2012-01-06 08:57:56
【问题描述】:

考虑以下代码:

/*
 * myclass.h
 */

class myclass_impl
{
    // ...
}

boost::shared_ptr<myclass_impl> myclass;

我能否以某种方式使myclass_impl(或至少直接使用它)成为它所定义的翻译单元的私有,允许客户只使用myclass typedef?我想要实现的是让编译器提醒我是否有人直接使用了实现类。

【问题讨论】:

  • 简短回答:否(客户始终可以使用myclass::value_type)。长答案:“使用”是什么意思?可以通过使某些构造函数和析构函数不可用来禁止某些用法。
  • 我的意思不仅是实例化,还包括对类的任何类型的引用(例如将其用作函数中的参数类型)。我担心在这种情况下可能无法将其设为私有:(
  • @dandrestor,有可能通过将整个类型设为私有(放入某个类)来使引用类无效:)

标签: c++ typedef private


【解决方案1】:

目前尚不清楚您要达到的目标。但是你已经被问到了,并且没有很清楚地澄清它。在您的回答评论中,您写道“我的意思不仅是 [禁止] 实例化,还包括对类的任何类型的引用(例如将其用作函数中的参数类型)”。

从表面上看,这意味着在你的头文件中使用以下内容:

struct BlahImpl;
typedef boost::shared_ptr<BlahImpl> Blah;

// Functions that create or give access to Blah instances.

然后客户端代码可以创建或访问Blah 实例,并复制它们(使用隐含的共享语义),但实际上不对它们做任何事情。充其量它们可以作为证明某些函数已被较早调用(产生一个实例)的证据。或者也许某些东西是由涉及此类实例的函数调用模式控制的,但无论如何,boost::shared_ptr 将完全不相关和多余。

因此,也许您的意思并不是您写的确切内容,而是类似“任何BlahImpl 实例应由boost::shared_ptr 动态分配和封装”。

如果是这样,您可以通过以下方式实现:

  • 您可以通过使析构函数非public,最好是protected,并提供一些销毁实例的方法来强制动态分配(最简单的,将friend-ship 授予通用销毁函数模板)。

  • 您可以通过多种方式确保按给定智能点进行包装。主要问题是转发构造函数参数。一种与 C++98 兼容的方法是通过宏进行转发,在这种情况下,“您不能无意中创建除通过此宏之外的实例”可以通过 混淆 来实现,即混淆new 表达式。

例子:

#include <boost/shared_ptr.hpp>
#include <iostream>
#include <stddef.h>         // ptrdiff_t, size_t
#include <string>
using namespace std;

namespace cpp11 {
    using boost::shared_ptr;
};

template< class Type >
void destroy( Type const* p ) { delete p; }

class OnlySharedPtrUsage
{
public:
    virtual ~OnlySharedPtrUsage() {}

    struct InstantiationObfuscation;

    static void* operator new( size_t size, InstantiationObfuscation* )
    {
        return ::operator new( size );
    }

    static void operator delete( void* p, InstantiationObfuscation* )
    {
        ::operator delete( p );
    }

    static void operator delete( void* p )
    {
        ::operator delete( p );
    }
};

#define NEW_SHARED( type, args )                                \
    ::cpp11::shared_ptr<type>(                                  \
        new( (type::InstantiationObfuscation*)0 ) type args,    \
        destroy<type>                                           \
        )

class MyClass
    : public OnlySharedPtrUsage     // The NEW_SHARED macro simplies.
{
template< class Type > friend void destroy( Type const* );
private:
    string  helloText_;

    MyClass( MyClass const& );                      // No such.
    MyClass& operator=( MyClass const& );           // No such.

protected:
    virtual ~MyClass()              // Only dynamic allocation allowed.
    {
        cout << "MyClass::<destroy>()" << endl;
    }

public:
    string helloText() const { return helloText_; }

    MyClass( string const& text )
        : helloText_( text )
    {
        cout << "MyClass::<init>( string )" << endl;
    }
};

int main()
{
    // MyClass     o( "a" );                   // ! Does not compile, not dynamic.
    // MyClass*     p  = new MyClass( "b" );   // ! Does not compile, not "mangled".
    cpp11::shared_ptr< MyClass > sp  = NEW_SHARED( MyClass,( "Hello from MyClass!" ) );

    cout << sp->helloText() << endl;
}

注意这个例子不直接支持make_shared的优化。混淆分配器功能(正式的放置功能)与make_shared 不太匹配。但我想可以通过定义一个分配器类并使用alloc_shared来完成。

另外请注意,这种方法仅支持标头模块;无需单独编译。 :-)

哦,对于一般情况,您还需要为数组添加分配器函数。

【讨论】:

    【解决方案2】:

    嗯……

    class myclass
    {
    private:
        class myclass_impl
        {
        public:
            void do_something() { std::cerr << "Hello there" << std::endl; }
        };
    
    public:
    
        typedef boost::shared_ptr<myclass_impl> ptr_type;
    
        static ptr_type construct()
        { return ptr_type(new myclass_impl()); }
    };
    
    int main()
    {    
        myclass::myclass_impl *x; // error: 'class myclass::myclass_impl' is private
        myclass::ptr_type::element_type *y; // ok
        myclass::ptr_type x = myclass::construct();
        x->do_something(); /// Hello there
    }
    

    这是你想要的吗?

    附:请注意,无法隐藏myclass_impl,因为boost::shared_ptr&lt;T&gt; 提供了对底层类型的访问。

    【讨论】:

      【解决方案3】:

      您可以创建接口myclass 并使用工厂方法来提供私有类myclass_impl 的实例,该类定义在与方法相同的文件中的匿名命名空间中。其他变体是 pimpl。

      文件 myclass.h

      class myclass
      {
      public:
          virtual void doSomething() = 0;
          static boost::shared_ptr<myclass> createInstance();
      };
      

      文件 myclass_impl.cpp

      #include "myclass.h"
      namespace {
          class myclass_impl : public myclass
          {
          public:
              virtual void doSomething() { std::cerr << "Hi there" << std::endl; }
          };
      }
      static boost::shared_ptr<myclass> myclass::createInstance()
      {
          return new myclass_impl();
      }
      

      更新(添加了非常糟糕的解决方案):

      myclass.h

      class destroyable { virtual ~destroyable() {} };
      class myclass {
      private:
          boost::shared_ptr<destroyable> pimpl;
      public:
          void doSomething();
      };
      

      myclass_impl.cpp

      namespace {
          class myclass_impl : public destroyable {
          public:
              void doSomething() { /* ... */ }
          };
      }
      void myclass::doSomething() { static_pointer_cast<myclass>(pimpl)->doSomething(); }
      

      【讨论】:

      • 好吧,在我的实现中myclass_impl 有私有构造函数,并且只由一个单独的友元工厂类创建。但是,我需要公开提供工厂类。你能详细说明一下你的答案吗?
      • 可以调整您的示例以使我不需要createInstance() 吗?我想通过更简单的语法myclass_object-&gt;method() 访问myclass_impl 的成员。
      • 通过工厂对象创建或直接通过静态工厂方法创建没有区别。我猜,制作私有构造函数只会在你使用myclass f()void f(myclass x) 的地方生效。但您仍然可以使用void f(myclass &amp;x)myclass *f()。实际上我认为您需要隐藏实现细节(即避免在头文件中显示private 部分)。看起来我安静不明白你想要达到的目标。你需要什么样的细节?
      • 我希望能够在我的客户端代码中声明void some_func(myclass_impl param)。如果我这样做了,我希望编译器提醒我。我只想能够做到void some_func(myclass param)
      • 关于实现细节,在我的头文件中显示私有部分(:P)很好。这不是我担心的。
      【解决方案4】:

      (免责声明,publicprivate 在这个答案中的使用是通用的,不是根据标准中的定义,而是意味着 accessible 或 可供其他翻译单位使用private 用于根据标准表示 公共成员

      这完全取决于类的特定定义,以及需要向其他翻译单元发布多少。

      如果您仅在 .cpp 文件中声明和定义类(我也会使用未命名的命名空间来避免名称冲突),则无法从该翻译单元外部访问该类型。

      如果这个类在必须发布到这​​个翻译单元之外的任何地方被引用(有一个公共类型的成员是一个指向公共类的指针/引用),那么下一个最好的事情就是只提供一个标头中的前向声明(现在在未命名的命名空间之外,可能是使用它的类内部的 private 类型)。

      作为最后的手段,您可以在标头中提供类型的完整定义(公共类型直接包含该类型的成员),但您仍然可以将类型设置为公共类型的私有内部类型,禁止在该类型(和它的朋友)之外使用。

      正如您所见,私有和公共的含义以及可以控制的内容存在差异。通过不在标头中提供定义并使用未命名的命名空间,您将使其 无法访问 其他 TU,通过仅提供前向声明,该类型 已知 存在,但不存在可在类型必须完整的任何上下文中使用(函数仍然可以获取并转发指向该类型的指针)。在单独的平面上,通过将其设置为内部并将private 设置为不同的类型,该定义将是已知,但在封闭类型的friends 之外将无法使用...

      【讨论】:

      • 谢谢。我想最接近我需要的是头文件中的前向声明,以及在 .cpp 文件中完全声明和定义的类。
      【解决方案5】:

      在源文件(不是头文件)中声明您的类,其他翻译单元将无法访问它。然后,使用前向声明在头文件中声明一个指针/引用。

      或者定义一个impl头文件并注释它不应该包含在其他源文件中。

      /*
       * myclass.h
       */
      
      class myclass_impl;
      
      class myclass
      {
        boost::shared_ptr<myclass_impl> myclass_i;
      public:
        myclass() : myclass_i(new myclass_impl) { }
        int getI() const;
      };
      
      
      /*
       * myclass.cpp
       */
      
      class myclass_impl
      {
        int i;
      public:
        myclass_impl() : i(4) { }
        int getI() const { return i; }  
      };
      
      int myclass::getI() const 
      { 
        return myclass_i->getI(); 
      }
      

      【讨论】:

      • 您能详细说明一下吗?通过在头文件中使用前向声明,该类不会对包含该头文件的所有其他翻译单元可用吗?
      • 是的,会的。但他们只会知道有一个类 myclass_impl,而不知道它的样子(因为您将类 myclass_impl 声明隐藏在 myclass.cpp 文件中)。
      • 这意味着在客户端代码中的某处使用指向 myclass_impl 的引用或指针不会导致编译器错误,这正是我想要实现的。我说的对吗?
      • 是的,但是由于您无法取消引用,他们会如何处理指针或引用?
      • 这回答了您的担忧:我想要实现的是让编译器提醒我是否有人直接使用了实现类。
      猜你喜欢
      • 2021-02-24
      • 1970-01-01
      • 1970-01-01
      • 2020-03-27
      • 2020-09-24
      • 2020-10-14
      • 2013-02-25
      • 2021-01-09
      • 2011-08-24
      相关资源
      最近更新 更多