Может ли класс быть приватным для единицы перевода? - PullRequest
6 голосов
/ 25 ноября 2011

Рассмотрим следующий код:

/*
 * myclass.h
 */

class myclass_impl
{
    // ...
}

boost::shared_ptr<myclass_impl> myclass;

Могу ли я как-то сделать myclass_impl (или, по крайней мере, его непосредственное использование) приватным для единицы перевода, в которой он определен, что позволит клиентамиспользовать только myclass typedef?Я пытаюсь добиться того, чтобы компилятор предупреждал меня, если кто-то использует класс реализации напрямую.

Ответы [ 5 ]

3 голосов
/ 25 ноября 2011

Вы можете создать интерфейс myclass и использовать фабричный метод для предоставления экземпляров вашего частного класса myclass_impl, определенного в анонимном пространстве имен в одном файле с методом.Другой вариант - pimpl.

Файл myclass.h

class myclass
{
public:
    virtual void doSomething() = 0;
    static boost::shared_ptr<myclass> createInstance();
};

Файл 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();
}

Обновление (добавлено совершенно ужасное решение):

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(); }
3 голосов
/ 25 ноября 2011

Объявите ваш класс в исходном файле (не в заголовочном файле), и он не будет доступен из других модулей перевода.Затем используйте предварительное объявление для объявления указателя / ссылки в вашем заголовочном файле.

Или определите заголовочный файл impl и прокомментируйте, что его не следует включать в другие исходные файлы.

1 голос
/ 26 ноября 2011

Хмм ...

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
}

Это то, что вы хотели?

P.S. Обратите внимание, что нет способа скрыть myclass_impl, потому что boost::shared_ptr<T> предоставляет доступ к базовому типу.

1 голос
/ 25 ноября 2011

Неясно, чего вы пытаетесь достичь. Но вас уже спрашивали об этом, и вы не смогли многое прояснить. В своем ответном комментарии вы написали: «Я имею в виду не только [запретить] создание экземпляров, но и любые ссылки на класс (например, использование его в качестве типа параметра в функции)».

Взятый по номиналу, это означает использование в заголовочном файле следующего:

struct BlahImpl;
typedef boost::shared_ptr<BlahImpl> Blah;

// Functions that create or give access to Blah instances.

Клиентский код может затем создавать или получать доступ к Blah экземплярам и копировать их (с подразумеваемой общей семантикой), но фактически ничего с ними не делать. В лучшем случае они могут служить доказательством того, что некоторая функция была вызвана ранее (производя экземпляр). Или, возможно, что-то контролируется шаблоном вызовов функций, включающих такие экземпляры, но в любом случае boost::shared_ptr будет тогда совершенно неактуальным и излишним.

Так что, возможно, вы имели в виду не совсем то, что написали, а что-то вроде «Любой BlahImpl экземпляр должен быть динамически выделен и инкапсулирован boost::shared_ptr».

И если это так, вы можете достичь этого следующим образом:

  • Вы можете принудительно применить динамическое распределение, сделав деструктор не public, предпочтительно protected, и предоставив некоторые средства для уничтожения экземпляров (самое простое, чтобы присвоить friend -ship на общем шаблоне функции уничтожения). ).

  • Вы можете обеспечить обертывание заданной интеллектуальной точкой несколькими способами. Основная проблема заключается в пересылке аргументов конструктора. Один из совместимых с C ++ 98 способов состоит в том, чтобы выполнить эту пересылку с помощью макроса, и в этом случае «вы не можете случайно создать экземпляры, отличные от этого макроса», может быть реализовано с помощью обфускации , а именно обфускации new выражение.

Пример:

#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;
}

Обратите внимание, что этот пример напрямую не поддерживает оптимизацию make_shared. Запутанная функция выделения (формально функция размещения) не совсем подходит для make_shared. Но я представляю, что это можно сделать, определив класс распределителя и используя alloc_shared.

Также обратите внимание, что этот подход поддерживает только модули заголовков; нет необходимости в отдельной компиляции. : -)

Да, и для общего случая вам также необходимо добавить функции-распределители для массивов.

Приветствия & hth.,

1 голос
/ 25 ноября 2011

(Отказ от ответственности, использование public и private в этом ответе является общим, и не соответствует определениям в стандарте, а скорее означает доступный или может использоваться другими единицами перевода. private используется для обозначения открытый член согласно стандарту)

Все зависит от конкретного определения класса, исколько нужно опубликовать в других единицах перевода.

Если вы объявляете и определяете класс только в файле .cpp (я бы также использовал безымянное пространство имен, чтобы избежать конфликтов имен), тогда тип не будетдоступный извне этого блока перевода.

Если на этот класс есть ссылка где-либо, где он должен быть опубликован за пределами этого блока перевода (есть член открытого типа, который является указателем / ссылкой на класс, который являетсяpublic), то следующая лучшая вещь - это просто указать в заголовке предварительное объявление (теперь за пределами безымянного пространства имен, возможно, как privateвведите внутренний для класса, который его использует).

В качестве крайней меры вы можете предоставить полное определение типа в заголовке (открытый тип содержит член этого типа напрямую), но вы по-прежнему можететип должен быть закрытым внутренним типом по отношению к общему типу, запрещая использование за пределами этого типа (и его друзей).

Как вы видите, существуют различия в том, что означает частный и публичный, и что можно контролироватьс этим.Не предоставляя определение в заголовке и используя безымянное пространство имен, вы делаете его недоступным для других TU, просто предоставляя прямые объявления, тип которых известен , чтобы существовать, но не может использоваться влюбой контекст, в котором тип должен быть завершен (функция все еще может принимать и перенаправлять указатели на тип).На отдельной плоскости, сделав его внутренним и private для другого типа, определение будет известно , но оно не будет использоваться за пределами friend s охватывающего типа ...

...