可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Encapsulation (information hiding) is a very useful concept, ensuring that only the barest minimal details are published in the API of a class.
But I can't help thinking that the way C++ does this is a little deficient. Take, for example, a (Celsius-based) temperature class like:
class tTemp {
private:
double temp;
double tempF (double);
public:
tTemp ();
~tTemp ();
setTemp (double);
double getTemp ();
double getTempF ();
};
Now, that's a very simple case but it illustrates a point that the encapsulation isn't perfect. "Real" encapsulation would hide all unnecessary information such as:
- the fact that the data is maintained internally in the
temp
variable (and its type).
- the fact that there is an internal routine for Fahrenheit/Celsius conversion.
So, ideally, it seems to me that the implementor of the class would use the above header but any client of the class would see just the public bits.
Don't get me wrong, I'm not criticising C++ since it meets the stated purpose of preventing clients from using the private bits but, for more complex classes, you could easily work out internal details based on the names, types and signatures of private data and functions.
How does C++ allow implementors to hide this information (assuming it is possible)? In C, I'd simply use an opaque type so that the internal details would be hidden but how would you do that in C++?
I suppose I could maintain an separate class, totally hidden from the client and known only to my own code, and then keep an instance of it with a void *
in the visible class (casting within my code), but that seems a rather painful process. Is there an easier way in C++ to achieve the same end?
回答1:
C++ uses an idiom known as "pimpl" (private implementation / pointer to implementation) to hide implementation details. Take a look at this MSDN article for details.
In short, you expose your interface in a header file as normal. Let's use your code as an example:
tTemp.h
class tTemp {
private:
class ttemp_impl; // forward declare the implementation class
std::unique_ptr<ttemp_impl> pimpl;
public:
tTemp ();
~tTemp ();
setTemp (double);
double getTemp (void);
double getTempF (void);
};
The public interface remains, but the private internals have been replaced with a smart pointer to a private implementation class. This implementation class is located only in the header's corresponding .cpp file, it is not exposed publicly.
tTemp.cpp
class tTemp::ttemp_impl
{
// put your implementation details here
}
// use the pimpl as necessary from the public interface
// be sure to initialize the pimpl!
tTtemp::tTemp() : pimpl(new ttemp_impl) {}
This also has the added advantage of allowing you to change the internals of your class without changing the header, which means less recompiling for users of your class.
For a full solution as shown in paxdiablo's pre-C++11 answer, but with unique_ptr
instead of void *
, you can use the following. First ttemp.h
:
#include <memory>
class tTemp {
public:
tTemp();
~tTemp();
void setTemp(double);
double getTemp (void);
double getTempF (void);
private:
class impl;
std::unique_ptr<impl> pimpl;
};
Next, the "hidden" implementation in ttemp.cpp
:
#include "ttemp.h"
struct tTemp::impl {
double temp;
impl() { temp = 0; };
double tempF (void) { return temp * 9 / 5 + 32; };
};
tTemp::tTemp() : pimpl (new tTemp::impl()) {};
tTemp::~tTemp() {}
void tTemp::setTemp (double t) { pimpl->temp = t; }
double tTemp::getTemp (void) { return pimpl->temp; }
double tTemp::getTempF (void) { return pimpl->tempF(); }
And, finally, ttemp_test.cpp
:
#include <iostream>
#include <cstdlib>
#include "ttemp.h"
int main (void) {
tTemp t;
std::cout << t.getTemp() << "C is " << t.getTempF() << "F\n";
return 0;
}
And, like paxdiablo's solution, the output is:
0C is 32F
with the added advantage of more type safety. This answer is the ideal solution for C++11, see paxdiablo's answer if your compiler is pre-C++11.
回答2:
Thought I would flesh out the "interface class / factory" technique that Don Wakefield mentions in his comment. To start with, we abstract away all implementation detail from the interface and define an abstract class that contains only the interface to a Temp
:
// in interface.h:
class Temp {
public:
virtual ~Temp() {}
virtual void setTemp(double) = 0;
virtual double getTemp() const = 0;
virtual double getTempF() const = 0;
static std::unique_ptr<Temp> factory();
};
Clients that want a Temp
object call the factory to build one. The factory could provide some complicated infrastructure that returns different implementations of the interface in different conditions, or something as simple as the "just give me a Temp" factory in this example.
It's possible for implementation classes to implement the interface by providing overrides for all of the pure virtual function declarations:
// in implementation.cpp:
class ConcreteTemp : public Temp {
private:
double temp;
static double tempF(double t) { return t * (9.0 / 5) + 32; }
public:
ConcreteTemp() : temp() {}
void setTemp(double t) { temp = t; }
double getTemp() const { return temp; }
double getTempF() const { return tempF(temp); }
};
and somewhere (possibly in the same implementation.cpp
) we need to define the factory:
std::unique_ptr<Temp> Temp::factory() {
return std::unique_ptr<Temp>(new ConcreteTemp);
}
This approach is a little more easily extensible than pimpl: anyone who wants to can implement the Temp
interface instead of there being only one "secret" implementation. There's also a bit less boilerplate since it's using the language's builtin mechanisms for virtual dispatch to dispatch interface function calls to implementations.
回答3:
Private implementation (PIMPL) is the way in which C++ can provide this feature. Since I had trouble getting the unique_ptr
variation to compile with CygWin g++ 4.3.4, another way to do it is to use a void *
within your visible class as follows. This will allow you to use pre-C++11 compilers, and compilers like the aforementioned gcc which only had experimental support for C++11.
First, the header file ttemp.h
, the one the client includes. This declares opaquely the internal implementation structure so that those internals are fully hidden. You can see that the only detail revealed is the name of the internal class and variable, neither of which need to reveal any information on how the internals work:
struct tTempImpl;
class tTemp {
public:
tTemp();
~tTemp();
tTemp (const tTemp&);
void setTemp(double);
double getTemp (void);
double getTempF (void);
private:
tTempImpl *pimpl;
};
Next, the implementation file ttemp.cpp
which both declares and defines the opaque stuff, and also defines the user-visible details. Since the user never sees this code, they do not know about how it's implemented:
#include "ttemp.h"
struct tTempImpl {
double temp;
tTempImpl() { temp = 0; };
double tempF (void) { return temp * 9 / 5 + 32; };
};
tTemp::tTemp() : pimpl (new tTempImpl()) {
};
tTemp::~tTemp() {
delete pimpl;
}
tTemp::tTemp (const tTemp& orig) {
pimpl = new tTempImpl;
pimpl->temp = orig.pimpl->temp;
}
void tTemp::setTemp (double t) {
pimpl->temp = t;
}
double tTemp::getTemp (void) {
return pimpl->temp;
}
double tTemp::getTempF (void) {
return pimpl->tempF();
}
Note that the internal implementation details are not protected in any way from the visible class itself. You could define the internals as a class with accessors and mutators but it seems unnecessary since it should be tightly coupled in this case.
One word of note from above: because you're using a pointer to control the hidden aspects, the default shallow copy constructor would cause grief by having two visible objects referring to the same private member (leading to a double-delete in the destructor). So you need to (as I have) provide a deep-copy copy constructor to prevent this.
Lastly, a test program showing how the whole thing hangs together:
#include <iostream>
#include "ttemp.h"
int main (void) {
tTemp t;
std::cout << t.getTemp() << "C is " << t.getTempF() << "F\n";
return 0;
}
The output of that code being, of course:
0C is 32F