2015-03-31 3 views
0

У меня есть несколько классов в библиотеке с внутренними элементами, которые я хочу скрыть от кода клиента. С точки зрения клиента, каждый класс запрашивается из класса библиотеки и используется только как непрозрачный указатель. Пример заключается в следующем:Скрытие членов в открытом интерфейсе и ODR

struct SomeSystem; 
void doSomethingToSomeSystem(SomeSystem* system, Parameters params); 
void doSomethingElseToSomeSystem(SomeSystem* system, Parameters params); 

На стороне реализации, SomeSystem имеет несколько пользователей, которые не являются видимыми для вызывающего абонента. Это все хорошо, но я не очень нравится неуклюжий синтаксис использования:

SomeSystem* system = lib->getSomeSystem(); 
doSomethingToSomeSystem(system, params); 
doSomethingElseToSomeSystem(system, params); 

Другой подход заключается в следующем:

struct SomeSystem; 
namespace somesystem { 
    void doSomething(SomeSystem* system, Parameters params); 
    void doSomethingElse(SomeSystem* system, Parameters params); 
} 

С кодом использования:

SomeSystem* system = lib->getSomeSystem(); 
somesystem::doSomething(system, params); 
somesystem::doSomethingElse(system, params); 

я мог также используют глобальные методы, называемые doSomething и doSomethingElse, и зависят от перегрузки функций, если другой тип также определяет doSomething. Однако в этом случае трудно найти всех «членов» SomeSystem в среде IDE.

я испытываю на самом деле использовать функции-члены:

struct SomeSystem { 
    void doSomething(Parameters params); 
    void doSomethingElse(Parameters params); 
}; 

С кодом использования:

SomeSystem* system = lib->getSomeSystem(); 
system->doSomething(params); 
system->doSomethingElse(params); 

Последний фрагмент выглядит хорошо для меня, но SomeSystem больше не является непрозрачным указатель - он фактически определяет членов. Я немного опасаюсь этого. Одной из потенциальных проблем является одно правило определения. Однако «публичное» определение и «личное» определение класса будут видны только для разных единиц перевода. Есть ли какая-то другая беда, скрытая здесь? Если код клиента пытается создать экземпляр SomeSystem в стеке или использовать новый, это, очевидно, приведет к краху программы. Но я согласен с этим. Возможно, я смогу обойти это, предоставив частный конструктор в открытом интерфейсе.

Другим подходом, конечно же, является определение абстрактного класса с использованием виртуальных методов. Однако я бы хотел избежать накладных расходов на это, если это не является абсолютно необходимым.

EDIT:

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

Общественный заголовок:

struct SomeSystem { 
    void doSomething(Parameters params); 
    void doSomethingElse(Parameters params); 
}; 

Частный Заголовок:

struct SomeSystem { 
    Member member; 
    void doSomething(Parameters params); 
    void doSomethingElse(Parameters params); 
}; 

Частный источник (включая частный заголовок):

void SomeSystem::doSomething(Parameters params) { 
    ... 
} 
void SomeSystem::doSomethingElse(Parameters params) { 
    ... 
} 

Это работает, когда я проверить это, но я не уверен, если он каким-то образом нарушает стандарт.Два заголовка никогда не включаются в одну единицу перевода.

+0

Вы знакомы с [Непрозрачный указатель/Ипподром Pimpl] (http://en.wikipedia.org/wiki/Opaque_pointer)? –

+0

Да, я знаком с этим, и я бы предпочел избежать этого. Он вводит дополнительное выделение памяти и усложняет реализацию. – rasmus

+0

Обратите внимание, что я специально после синтаксического сахара, не делая мое решение худшим качеством или сложностью. – rasmus

ответ

0

Идиома PIMPL, вероятно, идеальна в этой ситуации, но это дополнительное косвенное отношение к каждому доступу, так что это так.

Альтернативой, если вы только после того, как некоторые синтаксический сахар может быть, чтобы воспользоваться ADL - это будет по крайней мере, сохранить название системы из имени функции:

// publicly shared header file 
namespace one_system 
{ 
    struct system; 
    typedef system* system_handle; 
    void do_something(system_handle); 
}; 

// private implementation 
namespace one_system 
{ 
    struct system {}; 
    void do_something(system_handle) { cout << "one"; } 
}; 


int main() { 
    auto handle = /* SOMETHING TO GET THIS SYSTEM */; 
    do_something(handle); //do_something found by ADL 
    return 0; 
} 

EDIT:

Я все еще думаю, что PIMPL идеален. Вы также не обязательно должны иметь распределение или дополнительные накладные расходы по сравнению с тем, что у вас уже есть.

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

Все, что вам действительно нужно сделать, это определить интерфейс для класса так:

// put this in a namespace or name it according to the system 
class interface 
{ 
    system_handle m_system; 

    public: 
    interface(system_handle s) : m_system(s) {} 
    interface() = delete; 

    void do_something(); 
}; 

Теперь в другой единице трансляции, do_something() определяется для выполнения его работы в системе. Lib-> GetSystem() может вернуть экземпляр интерфейса. Интерфейс может быть полностью объявлен в файле заголовка, поскольку он состоит только из публичных функций. Система все еще полностью закрыта (в том случае, если пользователь lib не будет иметь заголовочный файл, объявляющий его содержимое).

Кроме того, интерфейс может быть тривиально скопирован пользователем. Не волнует, откуда это указатель, поэтому библиотека может передать адрес статики, если захочет.

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

Другим было бы то, что * это для интерфейса будет передано в do_something и, вероятно, не понадобится. Это может быть решена с помощью того do_something определены в файле заголовка тоже:

void do_something() { do_something_to_system(m_system); } 

Теперь компилятор должен иметь возможность оптимизировать даже * на это, так как do_something могут быть встраиваемыми и компилятор может легко просто вставить код для загрузки m_system в регистр и вызовите do_something_to_system (что было бы похоже на то, что вы упомянули в своих примерах).

+0

Я не согласен. PIMPL не идеален, поскольку он, как вы говорите, вводит дополнительную косвенность. Хуже того, он вводит дополнительное выделение памяти и увеличивает нагрузку на реализацию. Ваш пример с помощью one_system не будет работать, если вы не используете 'use namespace do_something'. Является также тем, что я обсуждал в своем вопросе. Спасибо за ваш ответ. – rasmus

+0

@rasmus Какая часть этого не будет работать? Вам не нужно делать с использованием пространства имен do_something для компилятора, чтобы найти функцию - вам просто нужно, чтобы дескриптор или система были объявлены в том же пространстве имен, что и функция. – qeadz

+0

Хорошо, что я плохой, я пропустил эту систему, определенную внутри пространства имен. Я все еще думаю, что проблема в том, что IDE не будет перечислять участников, если вы не наберете one_system :: хотя. – rasmus

Смежные вопросы