Reputation: 2531
Consider the following code:
/*
* myclass.h
*/
class myclass_impl
{
// ...
}
boost::shared_ptr<myclass_impl> myclass;
Can I somehow make myclass_impl
(or, at least, the direct use of it) be private to the translation unit it is defined in, allowing clients to only use the myclass
typedef? What I am trying to achieve is have the compiler alert me if somebody uses the implementation class directly.
Upvotes: 7
Views: 1752
Reputation: 145389
It is unclear what you're trying to achieve. But you have already been asked about that, and failed to clarify it very much. In your answer comment you wrote "I mean not only [to prohibit] instantiation, but any kind of reference to the class (such as using it as a parameter type in a function)".
Taken at face value, this means using the following in your header file:
struct BlahImpl;
typedef boost::shared_ptr<BlahImpl> Blah;
// Functions that create or give access to Blah instances.
Client code can then create or access Blah
instances, and copy them (with implied shared semantics), but not actually do anything with them. At best they can then serve as proof that some function has been called earlier (producing an instance). Or perhaps something is controlled by the pattern of calls to functions involving such instances, but anyway, the boost::shared_ptr
would then be completely irrelevant and superfluous.
So maybe you didn't mean exactly what you wrote, but rather something like "Any BlahImpl
instance shall be dynamically allocated and encapsulated by a boost::shared_ptr
".
And if so you can achieve that as follows:
You can enforce dynamic allocation by making the destructor non-public
, preferably protected
, and providing some means of destroying instances (the simplest, to confer friend
-ship on a common destroy function template).
You can ensure wrapping by a given smart point in a number of ways. The main problem is to forward constructor arguments. One C++98-compatible way is to do that forwarding via a macro, in which case the "you can not inadvertently create instances other than via this macro" can be implemented by obfuscation, namely obfuscation of the new
expression.
Example:
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <stddef.h> // ptrdiff_t, size_t
#include <string>
using namespace std;
namespace cpp11 {
using boost::shared_ptr;
};
template< class Type >
void destroy( Type const* p ) { delete p; }
class OnlySharedPtrUsage
{
public:
virtual ~OnlySharedPtrUsage() {}
struct InstantiationObfuscation;
static void* operator new( size_t size, InstantiationObfuscation* )
{
return ::operator new( size );
}
static void operator delete( void* p, InstantiationObfuscation* )
{
::operator delete( p );
}
static void operator delete( void* p )
{
::operator delete( p );
}
};
#define NEW_SHARED( type, args ) \
::cpp11::shared_ptr<type>( \
new( (type::InstantiationObfuscation*)0 ) type args, \
destroy<type> \
)
class MyClass
: public OnlySharedPtrUsage // The NEW_SHARED macro simplies.
{
template< class Type > friend void destroy( Type const* );
private:
string helloText_;
MyClass( MyClass const& ); // No such.
MyClass& operator=( MyClass const& ); // No such.
protected:
virtual ~MyClass() // Only dynamic allocation allowed.
{
cout << "MyClass::<destroy>()" << endl;
}
public:
string helloText() const { return helloText_; }
MyClass( string const& text )
: helloText_( text )
{
cout << "MyClass::<init>( string )" << endl;
}
};
int main()
{
// MyClass o( "a" ); // ! Does not compile, not dynamic.
// MyClass* p = new MyClass( "b" ); // ! Does not compile, not "mangled".
cpp11::shared_ptr< MyClass > sp = NEW_SHARED( MyClass,( "Hello from MyClass!" ) );
cout << sp->helloText() << endl;
}
Note that this example does not directly support the optimization of make_shared
. The obfuscated allocator function (formally a placement one) does not fit well with make_shared
. But I imagine that it can be done by defining an allocator class and using alloc_shared
.
Also note, this approach supports header only modules; no need for separate compilation. :-)
Oh, and for the general case you would need to also add allocator functions for arrays.
Upvotes: 1
Reputation: 13243
Hmm...
class myclass
{
private:
class myclass_impl
{
public:
void do_something() { std::cerr << "Hello there" << std::endl; }
};
public:
typedef boost::shared_ptr<myclass_impl> ptr_type;
static ptr_type construct()
{ return ptr_type(new myclass_impl()); }
};
int main()
{
myclass::myclass_impl *x; // error: 'class myclass::myclass_impl' is private
myclass::ptr_type::element_type *y; // ok
myclass::ptr_type x = myclass::construct();
x->do_something(); /// Hello there
}
Is that what you wanted?
P.S. Note that there is no way to hide myclass_impl
, because boost::shared_ptr<T>
provides access to underlying type.
Upvotes: 1
Reputation: 13243
You can create interface myclass
and use factory method to provide instances of you private class myclass_impl
defined in anonymous namespace in same file with method. Other variant is pimpl.
File myclass.h
class myclass
{
public:
virtual void doSomething() = 0;
static boost::shared_ptr<myclass> createInstance();
};
File myclass_impl.cpp
#include "myclass.h"
namespace {
class myclass_impl : public myclass
{
public:
virtual void doSomething() { std::cerr << "Hi there" << std::endl; }
};
}
static boost::shared_ptr<myclass> myclass::createInstance()
{
return new myclass_impl();
}
Update (added totally awful solution):
myclass.h
class destroyable { virtual ~destroyable() {} };
class myclass {
private:
boost::shared_ptr<destroyable> pimpl;
public:
void doSomething();
};
myclass_impl.cpp
namespace {
class myclass_impl : public destroyable {
public:
void doSomething() { /* ... */ }
};
}
void myclass::doSomething() { static_pointer_cast<myclass>(pimpl)->doSomething(); }
Upvotes: 3
Reputation: 208426
(Disclaimer, the use of public and private in this answer is generic, and not according to the definitions in the standard, but rather meaning accessible or usable by other translation units. private
used to signify public member as per the standard)
It all depends on the particular definition of the class, and how much needs to be published to other translation units.
If you declare and define the class only in the .cpp file (I would use an unnamed namespace also to avoid name collisions), then the type will not be accessible from outside of that translation unit.
If this class is referenced anywhere where it has to be published outside to this translation unit (there is a member of a public type that is a pointer/reference to class that is public) then the next best thing is providing just a forward declaration in the header (now outside of the unnamed namespace, possibly as a private
type internal to the class that uses it).
As a last resort, you can provide the whole definition of the type in the header (a public type holds a member of the type directly), but you can still have the type be a private internal type to the public type, disallowing usages outside of that type (and it's friends).
As you see there are differences as to what means private and public, and what can be controlled with it. By not providing the definition in the header and using an unnamed namespace you are making it inaccessible to other TU, by only providing forward declarations the type is known to exist, but not usable in any context where a type must be complete (a function could still take and forward pointers to the type). On a separate plane, by making it internal and private
to a different type the definition will be known but it will not be usable outside of the friend
s of the enclosing type...
Upvotes: 1
Reputation: 14392
Declare your class in the source file (not header file) and it will not be accessible from other translation units. Then, use forward declaration to declare a pointer/reference in your header file.
Or define a impl header file and comment that it should not be included in other source files.
/*
* myclass.h
*/
class myclass_impl;
class myclass
{
boost::shared_ptr<myclass_impl> myclass_i;
public:
myclass() : myclass_i(new myclass_impl) { }
int getI() const;
};
/*
* myclass.cpp
*/
class myclass_impl
{
int i;
public:
myclass_impl() : i(4) { }
int getI() const { return i; }
};
int myclass::getI() const
{
return myclass_i->getI();
}
Upvotes: 3