我正在使用 pimpl idiom 和 unique_ptr 编写一些代码。当我尝试使用类内初始化将 unique_ptr 默认设置为 nullptr 时,gcc 给出了编译错误,而 clang 和 msvc 都成功编译了代码。如果我没有使用类内初始化,错误就会消失。
\n\n// A.h\n#pragma once\n\n#include <memory>\n\nusing namespace std;\n\nclass B;\nclass A\n{\nprivate:\n ////////////////////////\n // here gives the error!\n ////////////////////////\n unique_ptr<B> impl{nullptr}; // error only with gcc, \n // ok with clang and msvc\n unique_ptr<B> impl2; // ok with all three\n\npublic:\n A();\n ~A();\n};\nRun Code Online (Sandbox Code Playgroud)\n\n// A.cpp\n#include "A.h"\n\nclass B\n{\nprivate:\n int b{5};\n\npublic:\n B() = default;\n ~B() = default;\n};\n\nA::A() = default;\nA::~A() = default;\nRun Code Online (Sandbox Code Playgroud)\n\n// main.cpp\n#include "A.h"\n\nint main()\n{\n A a;\n return 0;\n}\n …Run Code Online (Sandbox Code Playgroud) 这是一个非常无聊的错误,但我不知道这里发生了什么.
有大量的pimpl例子,但我不明白为什么这不起作用(这或多或少的例子之一,但我没有看到差异).
我有一个非常简单的Pimpl示例,但它不会工作.
// Foo.hpp
#include <boost/scoped_ptr.hpp>
class Foo
{
struct Bar;
//boost::scoped_ptr<Bar> pImpl;
Bar* pImpl;
public:
Foo();
~Foo() {}
int returnValue();
private:
};
Run Code Online (Sandbox Code Playgroud)
和
// Foo.cpp
#include "foo.hpp"
struct Foo::Bar
{
Bar() {}
~Bar() {}
int value;
};
Foo::Foo() : pImpl(new Bar())
{
pImpl->value = 7;
}
int Foo::returnValue() {
return *pImpl->value;
}
Run Code Online (Sandbox Code Playgroud)
编译这个给了我错误.C2100:非法间接.
谢谢.
根据我之前的问题,我希望a boost::shared_ptr<A>实际上是A(或许是A*)的子类,以便它可以用在A*作为参数的方法中.
考虑以下课程:
class A
{
public:
A(int x) {mX = x;}
virtual void setX(int x) {mX = x;}
virtual int getX() const {return mX;}
private:
int mX;
};
Run Code Online (Sandbox Code Playgroud)
在上一个问题中,我提议创建一个SharedA对象来处理这个问题,并且可能是这样做的.
class SharedA : public A
{
public:
SharedA(A* a) : mImpl(a){}
virtual void setX(int x) {mImpl->setX(x);}
virtual int getX() const {return mImpl->getX();}
private:
boost::shared_ptr<A> mImpl;
};
Run Code Online (Sandbox Code Playgroud)
如果我可以创建一个模板类来为我处理所有这些问题,那将是Grrrrrrrrreat想的.
template <class T>
class Shared : public T
{
public:
SharedT(T* t) : mImpl(t) …Run Code Online (Sandbox Code Playgroud) 我无法理解以下情况.它是关于使用基于std::unique_ptr派生类的pimpl习语.给定一个简单的类层次结构声明如下:
class Foo
{
public:
virtual ~Foo();
//...
};
struct X;
class Bar : public Foo
{
public:
~Bar();
//...
private:
std::unique_ptr<X> _d;
};
Run Code Online (Sandbox Code Playgroud)
我只显示与我的问题相关的代码.
想象一下,类Foo是一些接口,而类'Bar'是实现它的.我想用pimpl成语Bar.析构函数是虚拟的,并在相应的cpp文件中定义.此外struct X,只有前向声明的完整定义可以在cpp中访问,因此可以实例化析构函数的Bar析构函数std::unique_ptr<X>::~unique_ptr().当我尝试创建一个实例时Bar,我希望它能够工作
int main()
{
Bar b;
}
Run Code Online (Sandbox Code Playgroud)
相反,我得到编译错误使用未定义类型'X'后跟消息无法删除不完整类型(在Visual Studio 2013 Update 2中).但是,如果我显式添加默认构造函数Bar,则main()正确编译/构建.
class Foo
{
public:
virtual ~Foo();
};
struct X;
class Bar : public Foo
{
public:
Bar();
~Bar();
private:
std::unique_ptr<X> …Run Code Online (Sandbox Code Playgroud) 请参阅我继承的PIMPL实现.在派生类中,DerivedImpl继承自BaseImpl.
问题:指向Impl的指针是否只在基类中定义,如下面的代码?如果是这样,每次我需要使用基指针时,我必须将它转换为派生类型.但是,根据分析结果静态转换shared_ptr看起来很昂贵,因为这种强制转换被广泛使用.并且转换函数不能在标题中内联,因为它在那里是不完整的.
也许我犯了一些错误.或者使用智能指针有更好的实现吗?
// Base.h
class BaseImpl; // pre-declaration
class Base
{
public:
Base();
explicit Base(BaseImpl* ptr);
~Base();
protected:
std::shared_ptr<BaseImpl> d_Ptr;
};
Run Code Online (Sandbox Code Playgroud)
// baseimpl.h
class BaseImpl
{
double mDate;
};
Run Code Online (Sandbox Code Playgroud)
// Derived.h
#include "Base.h"
class DerivedImpl;
class Derived :
public Base
{
public:
Derived();
~Derived();
std::shared_ptr<DerivedImpl> d_func();
const std::shared_ptr<DerivedImpl> d_func() const;
};
Run Code Online (Sandbox Code Playgroud)
// Derived.cpp
#include "Derived.h"
#include "DerivedImpl.h"
Derived::Derived() : Base(new DerivedImpl())
{
}
Derived::~Derived()
{
}
std::shared_ptr<DerivedImpl> Derived::d_func()
{
return std::static_pointer_cast<DerivedImpl>(d_Ptr);
}
const std::shared_ptr<DerivedImpl> Derived::d_func() const
{
return std::static_pointer_cast<DerivedImpl>(d_Ptr); …Run Code Online (Sandbox Code Playgroud) 我已经阅读了一些关于 PIMPL 习惯用法的内容并且想知道 - 转发声明依赖类型有什么不同吗?
如果是这样的话:
特别考虑一个Foo依赖于Bar(应该有一个 type 成员)的类Bar。
Foo.h 带有前向声明:
class Bar;
class Foo
{
public:
Foo();
private:
Bar* _bar;
};
Run Code Online (Sandbox Code Playgroud)
Foo.h 与 PIMPL:
class Foo
{
public:
Foo();
private:
/* FooImpl is an incomplete type at this point.
* Implemented in cpp file and has a member of type Bar.
*/
class FooImpl;
FooImpl* _fooImpl;
}
Run Code Online (Sandbox Code Playgroud)
请忽略原始指针的使用 - 我只是想说明一点。
我的队友经常使用pimpl的变体,他喜欢这样:
foo.h中:
namespace { struct Impl; }
class Foo
{
public:
Foo();
~Foo();
void Bar(int n);
/* ... */
private:
std::unique_ptr<Impl> _impl;
};
Run Code Online (Sandbox Code Playgroud)
这里发生的事情是他正在声明实现类在匿名命名空间中.然后他将Impl在Foo.cpp中定义类.
因此,结构的定义::Impl将可供Foo.cpp翻译单位使用.其他代码包含Foo.h会引发警告,因为它们显然无法访问::Impl定义的内容Foo.cpp.但是,我们不需要它们 - 它只是一个Foo.cpp只用于它的类; 我们不希望它在其他地方可见或已知.
虽然我们当然可以在.cpp文件中包含多个标题,每个标题都声明自己的::Impl结构,但实际上并没有发生冲突,因为结构永远不会在各自的翻译单元之外使用.
tl; dr:这看起来很奇怪,引发警告,看起来好像会引起冲突,但似乎确实有效.
所有这一切,我不满意我的代码中提出了一些警告,这些警告已经深入到我们的代码中(这个文件越多,它就越难以取出.)这也只是一大堆警告.
我的队友坚持这一点,因为它很简单,保持代码定义简单,并允许我们Impl在所有代码中使用简短,一致的类名.
我不是编码惯例的坚持者; 如果这是我们用例的好习惯,我不介意.但是我觉得这是安全和可维护的,并且在某些时候不会在我们的脸上爆炸.
我目前正在研究pimpl习惯用法,并且有很好的教程介绍如何实现它(例如here)。但我从未见过将其实现为这样的基本模板类:
#ifndef PIMPL_H
#define PIMPL_H
template <class T>
class Pimpl
{
public:
explicit Pimpl();
explicit Pimpl(T *ptr);
virtual ~Pimpl() = 0;
Pimpl(const Pimpl<T> &other);
Pimpl &operator=(const Pimpl<T> &other);
protected:
T *d_ptr;
};
template<class T>
Pimpl<T>::Pimpl() : d_ptr(new T)
{
}
template<class T>
Pimpl<T>::Pimpl(T *ptr) : d_ptr(ptr)
{
}
template<class T>
Pimpl<T>::~Pimpl()
{
delete d_ptr;
d_ptr = 0;
}
template<class T>
Pimpl<T>::Pimpl(const Pimpl<T> &other) : d_ptr(new T(*other.d_ptr))
{
}
template<class T>
Pimpl<T> &Pimpl<T>::operator=(const Pimpl<T> &other)
{
if …Run Code Online (Sandbox Code Playgroud) 我经常使用“指向私有实现的指针”类。这些类的 setter 方法在技术上可以是 const 成员函数,例如:
class MyPrivateClass
{
public:
int something = 1;
};
class MyClass
{
public:
// TODO: initialize pointer in constructor
// TODO: delete pointer in destructor
// Note how this setter is const!
void setSomething(int something) const
{
p->something = something;
}
private:
MyPrivateClass* p;
};
int main()
{
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这是可能的,因为编译器只强制按位常量,而不是逻辑常量,所以上面的代码应该编译得很好。
我认为这些setter方法应该不为const成员函数,让来电者知道对象实际上是被修改(修改逻辑,而不是按位体改,由于指针实现)。
我的问题是:
是否有充分的理由使这些 setter 方法成为 const 成员函数?
Effective C++ 建议(在第 3 项中)尽可能始终使用 const,但我认为这不适用于我的示例。
我想用Pimpl(私有实现)创建一个类。通常,您会这样做:
class A
{
private:
class B;
B* _pimpl = nullptr;
}
Run Code Online (Sandbox Code Playgroud)
然后在.cpp文件中定义它。但是我必须使用动态分配。可以改用右值引用吗?
class A
{
public:
A(); //Constructor to init rvalue reference
private:
class B;
B&& _pimpl;
}
Run Code Online (Sandbox Code Playgroud)
然后在.cpp文件中:
class A::B
{
public:
int C = 3u;
}
//and then the constructor of A:
A::A() : _pimpl(B()) { } //now we should have a fresh b pimpl?
Run Code Online (Sandbox Code Playgroud)
我目前正在放假,只有C ++书籍可供参考。我阅读了有关右值引用的内容,并认为它可能有效。你们有什么感想?
c++ ×10
pimpl-idiom ×10
c++11 ×2
gcc8 ×1
inheritance ×1
namespaces ×1
reference ×1
shared-ptr ×1
templates ×1
unique-ptr ×1