【问题标题】:Typedef private struct prototype in source file源文件中的 Typedef 私有结构原型
【发布时间】:2013-12-16 20:27:49
【问题描述】:

在我的类中,我需要保留一个指向在我用来实现它的库中定义的结构的指针。由于这个库只在实现文件中使用,我想避免将它直接包含在头文件中。同时我想避免污染命名空间。因此我想做:

/* HEADER */
class Foo {
    private:
        struct ImplementationDetail;
        ImplementationDetail * p;
};
/* SOURCE */
#include <Library.h>
using Foo::ImplementationDetail = Library::SomeStruct;

但这不起作用,我目前正在使用 PIMPL:

/* HEADER */
class Foo {
    private:
        struct ImplementationDetail;
        ImplementationDetail * p_;
};
/* SOURCE */
#include <Library.h>
struct ImplementationDetail {
    Library::SomeStruct * realp_; 
}

有没有办法避免双重取消引用?由于未知的指针大小,我的第一个解决方案无法正常工作的原因是什么?

【问题讨论】:

  • '我的第一个解决方案是由于未知的指针大小而无法工作的原因吗?''non-working' 非常模糊,您具体的编译/运行时问题是什么?
  • 这取决于我将实际原型放在哪里。如果它是私有的,它会抱怨这一点。如果它是公开的,它会抱怨 Foo 不是命名空间。
  • 您可以查找私有实现模式 (PIMPL)。我相信我在大型设计模式书籍中读到过它。

标签: c++ pointers implementation pimpl-idiom


【解决方案1】:
// Header
class Foo {
    private:
        struct ImplementationDetail;
        ImplementationDetail * p;
};

// Source
#include <Library.h>
struct Foo::ImplementationDetail :public Library::SomeStruct {
   // ....
};

并且只在这个源文件中分配/解除分配/取消引用指针应该可以正常工作。

【讨论】:

  • 无论如何,如果库包含像 SomeStruct * getNewStruct() 这样的方法,这将起作用?
【解决方案2】:

这是一个错误的声明:

using Foo::ImplementationDetail = Library::SomeStruct;

using 不能这样工作。在 C++11 中,using 无法为一个命名空间中的名称创建另一个命名空间中的名称的别名。在 C++03 中,using 所做的只是将其他命名空间引入当前翻译单元中的全局可见性。它不用于在 C++03 中创建别名,就像您在这里似乎想要做的那样。

pimpl 是做你想做的事情的事实上的方法,但是在你的头文件中而不是尝试使用ImplementationDetail*,我会使用一个简单的void* .根据标准,保证以这种方式使用void* 是正确的:

class Foo {
    private:
        void * pImpl;

使用static_cast2void* 转换为您的实际类型:

void Foo::Bar()
{
  Library::SomeStruct* thingy = static_cast <Library::SomeStruct*> (pImpl);
  // ...
}

您可以通过前向声明您的库类型来避免以一致的方式使用void*

namespace Library
{
  struct SomeStruct;
};

class Foo
{
private:
  Library::SomeStruct* pStruct;
};

然后在实现中不需要丑陋的演员表。


2使用static_cast:或reinterpret_cast

【讨论】:

  • 你不能static_cast 到任何从无效的东西吗?
  • @MarkB:是的,你当然可以。我想这通常是首选——由于习惯,我使用reinterpret_castparty,部分原因是我认为它对其他程序员来说更清楚一些。后者可能是错误的。
  • 在 C++11 中,using 也用于声明类型别名。唯一的问题是您不能将声明放在不同的范围内。
  • 我认为第一段应该改写,而不是只用注释链接。
【解决方案3】:

您不能采用第一种方法的原因是,在标题中您告诉编译器“我在 Foo 中声明了一个嵌套类,它被称为 ImplementationDetail”。然后你继续说“等等等等,它不是一个新类,它完全是另一个东西的别名”,编译器会感到困惑是可以理解的。

您是否尝试过只转发声明库的实现并使用它而不是尝试创建别名?

【讨论】:

  • 我已经考虑过了。问题是我使用的库是 C 语言的,因此我会创建一个全局名称供所有人查看,但没人真正关心。
【解决方案4】:

在您的第一个代码中,您将嵌套类型 ImplementationDetail 声明为 struct,它将在 Foo 中定义。尝试给它取别名是行不通的,因为那将是在别处定义的类型,实际上,您的 private 结构无法从类外部访问。在内部包装指向另一个对象的指针似乎是不必要的:您可以改为按值嵌入Library::SomeStruct,或者让您的ImplementationDetail 派生自Library::SomeStruct

struct ImplementationDetail
    : Library::SomeStruct {
    using Library::SomeStruct::SomeStruct;
};

using 声明只是用来继承Library::SomeStruct 的所有构造函数)。

【讨论】:

    【解决方案5】:

    我认为没有强制转换是不可能的。

    基本上有两种方法:

    1) 将 p_ 定义为 void* 并将其强制转换为每个使用它的函数。

    /* HEADER */
    class Foo {
        private:
            void* p;
    };
    
    /* SOURCE */
    #include <Library.h>
    
    void Foo::AnyFunc()
    {
        Library::SomeStruct* pImpl = reinterpret_cast<Library::SomeStruct*>(p);
        ...
    }
    

    2) 创建一个类的“阴影”类(在 .cpp 文件中),其中所有成员都被克隆并且 p_ 定义为 Library::SomeStruct。然后将this-指针投射到这个阴影类。这当然是一个非常不安全和肮脏的黑客,我不推荐......

    /* HEADER */
    class Foo {
        private:
            void* p;
    };
    
    /* SOURCE */
    #include <Library.h>
    
    class FooImpl
    {
    public:
        void AnyFunc() { p->DoSomething(); }
    
    private:
        Library::SomeStruct* p;
    }
    
    void Foo::AnyFunc()
    {
        FooImpl* pImpl = reinterpret_cast<FooImpl*>(this);
        pImpl->AnyFunc();
    }
    

    这会利用内存结构,因此非常脆弱(所有成员都需要按相同的顺序排列,当您添加或删除成员时,您也需要更新 ShadowFoo)。我提到这一点只是为了完整。

    3) 这给我们带来了另一种更简单的方法:在源文件中创建实现并在构造函数中使用void*-指针对其进行初始化:

    /* HEADER */
    class Foo {
        private:
            void* p;
    };
    
    /* SOURCE */
    #include <Library.h>
    
    class FooImpl
    {
    public:
        FooImpl(void* pSomeStruct)
        {
            p = reinterpret_cast<Library::SomeStruct*>(pSomeStruct);
        }
    
        void AnyFunc() { p->DoSomething(); }
    
    private:
        Library::SomeStruct* p;
    }
    
    void Foo::AnyFunc()
    {
        FooImpl impl = FooImpl(p);
        impl.AnyFunc();
    }
    

    【讨论】:

    • 只要您展示工作代码示例,我就会支持您的答案(而不是其他已经正确的答案)。
    猜你喜欢
    • 1970-01-01
    • 2013-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-21
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    相关资源
    最近更新 更多