声明类和方法但不声明成员的 C++ 头文件?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/778802/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C++ Header file that declares a class and methods but not members?
提问by David Grayson
Is it possible to make a C++ header file (.h) that declares a class, and its public methods, but does not define the private members in that class? I found a few pagesthat say you should declare the class and all its members in the header file, then define the methods separately in you cpp file. I ask because I want to have a class that is defined in a Win32 DLL, and I want it to be properly encapsulated: the internal implementation of that class might change, including its members, but these changes should not affect code that uses the class.
是否可以创建一个 C++ 头文件 (.h) 来声明一个类及其公共方法,但没有定义该类中的私有成员?我发现有几页说你应该在头文件中声明类及其所有成员,然后在你的 cpp 文件中单独定义方法。我问是因为我想要一个在 Win32 DLL 中定义的类,并且我希望它被正确封装:该类的内部实现可能会发生变化,包括其成员,但这些变化不应影响使用该类的代码.
I guess that if I had this, then it would make it impossible for the compiler to know the size of my objects ahead of time. But that should be fine, as long as the compiler is smart enough to use the constructor and just pass around pointers to the location in memory where my object is stored, and never let me run "sizeof(MyClass)".
我想如果我有这个,那么编译器就不可能提前知道我的对象的大小。但这应该没问题,只要编译器足够聪明,可以使用构造函数并且只传递指向存储对象的内存位置的指针,并且永远不要让我运行“sizeof(MyClass)”。
Update:Thanks to everyone who answered! It seems like the pimpl idiom is a good way to achieve what I was talking about. I'm going to do something similar:
更新:感谢所有回答的人!似乎 pimpl 成语是实现我所谈论的内容的好方法。我要做类似的事情:
My Win32 DLL file will have a bunch of separate functions like this:
我的 Win32 DLL 文件将有一堆这样的独立函数:
void * __stdcall DogCreate();
int __stdcall DogGetWeight(void * this);
void __stdcall DogSetWeight(void * this, int weight);
This is the typical way the Microsoft writes their DLL files so I think there is probably good reason for it.
这是 Microsoft 编写其 DLL 文件的典型方式,因此我认为可能有充分的理由。
But I want to take advantage of the nice syntax C++ has for classes, so I'll write a wrapper class to wrap up all of these functions. It will have one member, which will be "void * pimpl". This wrapper class will be so simple that I might as well just declare it AND define it in the header file. But this wrapper class really has no purposes other than making the C++ code look pretty as far as I can tell.
但是我想利用 C++ 为类提供的良好语法,因此我将编写一个包装类来包装所有这些函数。它将有一个成员,即“void * pimpl”。这个包装类将非常简单,我不妨直接声明它并在头文件中定义它。但是,据我所知,这个包装类除了使 C++ 代码看起来很漂亮之外,真的没有其他用途。
回答by Doug T.
I think what you are looking for is something called the "pimpl idiom". To understand how this works, you need to understand that in C++ you can forward declare something like so.
我认为你正在寻找的是一种叫做“pimpl idiom”的东西。要了解这是如何工作的,您需要了解在 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
};
So to forward declare means to promise to fully declare a type later. Its saying "there will be this thing called a CWidget, I promise. I'll tell you more about it later.".
所以转发声明意味着承诺稍后完全声明一个类型。它说“会有一个叫做CWidget的东西,我保证。我稍后会告诉你更多。”。
The rules of forward declaration say that you can define a pointer or a reference to something that has been forward declared. This is because pointers and references are really just addresses-a number where this yet-to-be-defined thing will be. Being able to declare a pointer to something without fully declaring it is convenient for a lot of reasons.
前向声明的规则说你可以定义一个指针或对已经被前向声明的东西的引用。这是因为指针和引用实际上只是地址——这个尚未定义的事物所在的数字。由于很多原因,能够在不完全声明的情况下声明指向某物的指针是很方便的。
Its useful here because you can use this to hide some of the internals of a class using the "pimpl" method. Pimpl means "pointer to implementation". So instead of "widget" you have a class that is the actual implementation. The class you are declaring in your header is just a pass-through to the CImpl class. Here's how it works:
它在这里很有用,因为您可以使用它来隐藏使用“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
};
Thing.cpp has CThing's methods defined as pass-throughs to the impl:
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();
}
tada! You've hidden all the details in your cpp and your header file is a very tidy list of methods. Its a great thing. The only thing you might see different from the template above is that people may use boost::shared_ptr<> or other smart pointer for the impl. Something that deletes itself.
多多!你已经在你的 cpp 中隐藏了所有的细节,你的头文件是一个非常整洁的方法列表。这是一件很棒的事情。您可能会看到与上面的模板唯一不同的是,人们可能会使用 boost::shared_ptr<> 或其他智能指针来实现。自我删除的东西。
Also, keep in mind this method comes with some annoyances. Debugging can be a tad bit annoying (extra level of redirection to step through). Its also a lot of overhead for creating a class. If you do this for every class, you'll get tired of all the typing :).
另外,请记住,这种方法会带来一些烦恼。调试可能有点烦人(额外的重定向级别)。创建类的开销也很大。如果你对每节课都这样做,你会厌倦所有的打字:)。
回答by John Dibling
The pimpl idiomadds a void* private data member to your class, and this is a useful technique if you need something quick & dirty. It has its drawbacks however. Main among those is it makes it difficult to use polymorphism on the abstract type. Sometimes you might want an abstract base class and subclasses of that base class, collect pointers to all the different types in a vector and call methods on them. In addition, if the purpose of the pimpl idiom is to hide the implementation details of the class then it only almostsucceeds: the pointer itself is an implementation detail. An opaque implementation detail, perhaps. But an implementation detail nonetheless.
该PIMPL方法增加了一个void *私有数据成员到您的类,这是,如果你需要的东西快速和肮脏的一个有用的技术。然而,它有它的缺点。其中最主要的是它使得在抽象类型上使用多态变得困难。有时您可能需要一个抽象基类和该基类的子类,收集指向向量中所有不同类型的指针并调用它们的方法。此外,如果 pimpl 习惯用法的目的是隐藏类的实现细节,那么它几乎只能成功:指针本身就是一个实现细节。也许是一个不透明的实现细节。但仍然是一个实现细节。
An alternative to the pimpl idiom exists which can be used to remove all of the implementation details from the interface while providing a base type that can be used polymorphically, if needed.
存在 pimpl 惯用语的替代方法,它可用于从接口中删除所有实现细节,同时提供可在需要时多态使用的基类型。
In your DLL's header file (the one #included by client code) create an abstract class with only public methods and concepts which dictate how the class is to be instantiated (eg, public factory methods & clone methods):
在 DLL 的头文件(客户端代码中#included 的头文件)中创建一个抽象类,其中仅包含公共方法和概念,这些方法和概念决定了如何实例化该类(例如,公共工厂方法和克隆方法):
kennel.h
狗窝.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*
};
};
...Animal is an abstract base classand so cannot be instantiated; no private ctor needs to be declared. The presence of the virtual dtor ensures that if someone delete
s an Animal*
, the proper subclass' dtor will also be called.
...Animal 是一个抽象基类,因此不能被实例化;不需要声明私有 ctor。虚拟 dtor 的存在确保如果有人delete
是Animal*
,那么适当的子类 dtor 也将被调用。
In order to implement different subclasses of the base type (eg dogs & cats), you would declare implementation-level classes in your DLL. These classes derive ultimately from the abstract base class you declared in your header file, and the factory methods would actually instantiate one of these subclasses.
为了实现基本类型的不同子类(例如狗和猫),您需要在 DLL 中声明实现级别的类。这些类最终派生自您在头文件中声明的抽象基类,并且工厂方法实际上会实例化这些子类之一。
dll.cpp:
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";
}
Now you have the interface defined in the header & the implementation details completely seperated out where client code can't see it at all. You would use this by calling methods declared in your header file from code that links to your DLL. Here's a sample driver:
现在您在头文件中定义了接口,并且实现细节完全分开,客户端代码根本看不到它。您可以通过从链接到您的 DLL 的代码调用在您的头文件中声明的方法来使用它。这是一个示例驱动程序:
main.cpp:
主.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;
}
回答by BubbaT
Google "pimple idiom" or "handle C++".
谷歌“疙瘩成语”或“处理C++”。
回答by Mark Ransom
Yes, this can be a desireable thing to do. One easy way is to make the implementation class derive from the class defined in the header.
是的,这可能是一件令人向往的事情。一种简单的方法是使实现类从头文件中定义的类派生。
The downside is that the compiler won't know how to construct your class, so you'll need some kind of factory method to get instances of the class. It will be impossible to have local instances on the stack.
缺点是编译器不知道如何构造您的类,因此您需要某种工厂方法来获取类的实例。堆栈上不可能有本地实例。
回答by mmmmmmmm
You have to declare all members in the header so the compiler knows how large is the object and so on.
您必须在头文件中声明所有成员,以便编译器知道对象有多大等等。
But you can solve this by using an interface:
但是你可以通过使用一个接口来解决这个问题:
ext.h:
分机.h:
class ExtClass
{
public:
virtual void func1(int xy) = 0;
virtual int func2(XYClass ¶m) = 0;
};
int.h:
内部.h:
class ExtClassImpl : public ExtClass
{
public:
void func1(int xy);
int func2(XYClass¶m);
};
int.cpp:
内部cpp:
void ExtClassImpl::func1(int xy)
{
...
}
int ExtClassImpl::func2(XYClass¶m)
{
...
}
回答by aJ.
Is it possible to make a C++ header file (.h) that declares a class, and its public methods, but does not declare the private members in that class?
是否可以创建一个 C++ 头文件 (.h) 来声明一个类及其公共方法,但不声明该类中的私有成员?
The most nearest answer is PIMPL idiom.
最接近的答案是 PIMPL 成语。
Refer this The Fast Pimpl Idiomfrom Herb Sutter.
请参阅Herb Sutter 的The Fast Pimpl Idiom。
IMO Pimpl is really useful during initial stages of development where your header file is going to change many times. Pimpl has its cost due to its allocation\deallocation of internal object on heap.
IMO Pimpl 在开发的初始阶段非常有用,因为您的头文件将多次更改。Pimpl 有其成本,因为它在堆上分配/释放内部对象。
回答by J.W.
Check out the class The Handle-BodyIdiom in C++
查看C++ 中的The Handle-BodyIdiom类