Dav*_*son 19 c++ encapsulation header class file
是否可以创建一个C++头文件(.h)来声明一个类及其公共方法,但是没有定义该类中的私有成员?我发现有几页说你应该在头文件中声明该类及其所有成员,然后在你的cpp文件中单独定义方法.我问,因为我想有在一个Win32 DLL中定义一个类,我希望它是正确的封装:在内部实现这个类的可能会发生变化,包括它的成员,但这些变化不会影响使用该类的代码.
我想如果我有这个,那么它将使编译器无法提前知道我的对象的大小.但是,这应该是罚款,只要编译器是足够聪明的使用构造,只是指针传递给周围的位置在我的对象存储内存,并且从来没有让我跑"的sizeof(MyClass的)".
更新: 感谢所有回答的人!似乎pimpl成语是实现我所谈论的好方法.我要做类似的事情:
我的Win32 DLL文件将有一堆单独的函数,如下所示:
void * __stdcall DogCreate();
int __stdcall DogGetWeight(void * this);
void __stdcall DogSetWeight(void * this, int weight);
Run Code Online (Sandbox Code Playgroud)
这是Microsoft编写DLL文件的典型方式,因此我认为可能有很好的理由.
但是我想利用C++对类的优秀语法,所以我将编写一个包装类来包装所有这些函数.它将有一个成员,这将是"无效*pimpl".这个包装器类将非常简单,我可能只是声明它并在头文件中定义它.但是这个包装类除了让C++代码看起来很漂亮外没有任何其他目的.
Dou*_* T. 33
我认为你所寻找的东西叫做"pimpl成语".要理解它是如何工作的,你需要理解在C++中你可以转发声明类似的东西.
class CWidget; // Widget will exist sometime in the future
CWidget* aWidget; // An address (integer) to something that
// isn't defined *yet*
// later on define CWidget to be something concrete
class CWidget
{
// methods and such
};
Run Code Online (Sandbox Code Playgroud)
因此,转发声明意味着承诺稍后完全声明一个类型.它说"我会保证这个东西叫CWidget.我稍后会告诉你更多关于它的事情."
前向声明的规则说你可以定义一个指针或对前向声明的东西的引用.这是因为指针和引用实际上只是地址 - 这个尚未定义的东西的数字.能够在没有完全声明的情况下声明指向某个东西的指针很方便.
它在这里很有用,因为你可以使用它来隐藏类的一些内部使用"pimpl"方法.Pimpl意为"指向实现的指针".因此,除了"小部件"之外,您还有一个实际实现的类.您在标题中声明的类只是CImpl类的传递.以下是它的工作原理:
// Thing.h
class CThing
{
public:
// CThings methods and constructors...
CThing();
void DoSomething();
int GetSomething();
~CThing();
private:
// CThing store's a pointer to some implementation class to
// be defined later
class CImpl; // forward declaration to CImpl
CImpl* m_pimpl; // pointer to my implementation
};
Run Code Online (Sandbox Code Playgroud)
Thing.cpp将CThing的方法定义为impl的传递:
// Fully define Impl
class CThing::CImpl
{
private:
// all variables
public:
// methods inlined
CImpl()
{
// constructor
}
void DoSomething()
{
// actual code that does something
}
//etc for all methods
};
// CThing methods are just pass-throughs
CThing::CThing() : m_pimpl(new CThing::CImpl());
{
}
CThing::~CThing()
{
delete m_pimpl;
}
int CThing::GetSomething()
{
return m_pimpl->GetSomething();
}
void CThing::DoSomething()
{
m_impl->DoSomething();
}
Run Code Online (Sandbox Code Playgroud)
田田!你已经隐藏了cpp中的所有细节,你的头文件是一个非常整洁的方法列表.这是一件好事.你可能会看到与上面的模板不同的唯一一点是人们可能会使用boost :: shared_ptr <>或其他智能指针进行impl.有些东西会自行删除.
另外,请记住这种方法带来一些烦恼.调试可能有点烦人(额外的重定向级别).创建一个类也需要很多开销.如果你为每个班级都这样做,你会厌倦所有打字:).
该PIMPL方法增加了一个void*私有数据成员到您的类,这是一个有用的技术,如果你需要的东西快速和肮脏.然而,它有它的缺点.其中主要是它使得在抽象类型上使用多态变得困难.有时您可能需要一个抽象基类和该基类的子类,收集指向向量中所有不同类型的指针并调用它们上的方法.另外,如果pimpl习语的目的是隐藏类的实现细节,那么它几乎成功:指针本身就是一个实现细节.也许是一个不透明的实现细节.但是仍然是一个实现细节.
存在pimpl习语的替代方案,其可用于从接口移除所有实现细节,同时提供可以多态地使用的基本类型(如果需要).
在DLL的头文件(由客户端代码包含的#)中创建一个抽象类,其中只包含公共方法和概念,这些方法和概念规定了如何实例化类(例如,公共工厂方法和克隆方法):
kennel.h
/****************************************************************
***
*** The declaration of the kennel namespace & its members
*** would typically be in a header file.
***/
// Provide an abstract interface class which clients will have pointers to.
// Do not permit client code to instantiate this class directly.
namespace kennel
{
class Animal
{
public:
// factory method
static Animal* createDog(); // factory method
static Animal* createCat(); // factory method
virtual Animal* clone() const = 0; // creates a duplicate object
virtual string speak() const = 0; // says something this animal might say
virtual unsigned long serialNumber() const = 0; // returns a bit of state data
virtual string name() const = 0; // retuyrns this animal's name
virtual string type() const = 0; // returns the type of animal this is
virtual ~Animal() {}; // ensures the correct subclass' dtor is called when deleteing an Animal*
};
};
Run Code Online (Sandbox Code Playgroud)
... Animal是一个抽象基类,因此无法实例化; 不需要声明私人ctor.虚拟dtor的存在确保如果某人delete是Animal*,则也将调用正确的子类'dtor.
为了实现基类型的不同子类(例如狗和猫),您将在DLL中声明实现级别的类.这些类最终来自您在头文件中声明的抽象基类,而工厂方法实际上将实例化其中一个子类.
dll.cpp:
/****************************************************************
***
*** The code that follows implements the interface
*** declared above, and would typically be in a cc
*** file.
***/
// Implementation of the Animal abstract interface
// this implementation includes several features
// found in real code:
// Each animal type has it's own properties/behavior (speak)
// Each instance has it's own member data (name)
// All Animals share some common properties/data (serial number)
//
namespace
{
// AnimalImpl provides properties & data that are shared by
// all Animals (serial number, clone)
class AnimalImpl : public kennel::Animal
{
public:
unsigned long serialNumber() const;
string type() const;
protected:
AnimalImpl();
AnimalImpl(const AnimalImpl& rhs);
virtual ~AnimalImpl();
private:
unsigned long serial_; // each Animal has its own serial number
static unsigned long lastSerial_; // this increments every time an AnimalImpl is created
};
class Dog : public AnimalImpl
{
public:
kennel::Animal* clone() const { Dog* copy = new Dog(*this); return copy;}
std::string speak() const { return "Woof!"; }
std::string name() const { return name_; }
Dog(const char* name) : name_(name) {};
virtual ~Dog() { cout << type() << " #" << serialNumber() << " is napping..." << endl; }
protected:
Dog(const Dog& rhs) : AnimalImpl(rhs), name_(rhs.name_) {};
private:
std::string name_;
};
class Cat : public AnimalImpl
{
public:
kennel::Animal* clone() const { Cat* copy = new Cat(*this); return copy;}
std::string speak() const { return "Meow!"; }
std::string name() const { return name_; }
Cat(const char* name) : name_(name) {};
virtual ~Cat() { cout << type() << " #" << serialNumber() << " escaped!" << endl; }
protected:
Cat(const Cat& rhs) : AnimalImpl(rhs), name_(rhs.name_) {};
private:
std::string name_;
};
};
unsigned long AnimalImpl::lastSerial_ = 0;
// Implementation of interface-level functions
// In this case, just the factory functions.
kennel::Animal* kennel::Animal::createDog()
{
static const char* name [] = {"Kita", "Duffy", "Fido", "Bowser", "Spot", "Snoopy", "Smkoky"};
static const size_t numNames = sizeof(name)/sizeof(name[0]);
size_t ix = rand()/(RAND_MAX/numNames);
Dog* ret = new Dog(name[ix]);
return ret;
}
kennel::Animal* kennel::Animal::createCat()
{
static const char* name [] = {"Murpyhy", "Jasmine", "Spike", "Heathcliff", "Jerry", "Garfield"};
static const size_t numNames = sizeof(name)/sizeof(name[0]);
size_t ix = rand()/(RAND_MAX/numNames);
Cat* ret = new Cat(name[ix]);
return ret;
}
// Implementation of base implementation class
AnimalImpl::AnimalImpl()
: serial_(++lastSerial_)
{
};
AnimalImpl::AnimalImpl(const AnimalImpl& rhs)
: serial_(rhs.serial_)
{
};
AnimalImpl::~AnimalImpl()
{
};
unsigned long AnimalImpl::serialNumber() const
{
return serial_;
}
string AnimalImpl::type() const
{
if( dynamic_cast<const Dog*>(this) )
return "Dog";
if( dynamic_cast<const Cat*>(this) )
return "Cat";
else
return "Alien";
}
Run Code Online (Sandbox Code Playgroud)
现在,您已经在标头中定义了接口,并且实现细节完全独立于客户端代码根本看不到它的位置.您可以通过从链接到DLL的代码调用头文件中声明的方法来使用它.这是一个示例驱动程序:
main.cpp中:
std::string dump(const kennel::Animal* animal)
{
stringstream ss;
ss << animal->type() << " #" << animal->serialNumber() << " says '" << animal->speak() << "'" << endl;
return ss.str();
}
template<class T> void del_ptr(T* p)
{
delete p;
}
int main()
{
srand((unsigned) time(0));
// start up a new farm
typedef vector<kennel::Animal*> Animals;
Animals farm;
// add 20 animals to the farm
for( size_t n = 0; n < 20; ++n )
{
bool makeDog = rand()/(RAND_MAX/2) != 0;
if( makeDog )
farm.push_back(kennel::Animal::createDog());
else
farm.push_back(kennel::Animal::createCat());
}
// list all the animals in the farm to the console
transform(farm.begin(), farm.end(), ostream_iterator<string>(cout, ""), dump);
// deallocate all the animals in the farm
for_each( farm.begin(), farm.end(), del_ptr<kennel::Animal>);
return 0;
}
Run Code Online (Sandbox Code Playgroud)