Фон
Я изучаю, как реализовать идиому pimpl, используя новый метод c ++ 11, описанный Хербом Саттером на этой странице: https://herbsutter.com/gotw/_100/
Я пытаюсь изменить этот пример, добавив переменную-член в частную реализацию, в частности std :: string (хотя char * имеет ту же проблему).
Проблема
Это кажется невозможным из-за использования нецелого типа static const. Инициализация внутри класса может выполняться только для целых типов, но, поскольку она статична, ее также нельзя инициализировать в конструкторе.
Решение этой проблемы - объявить частную переменную в файле заголовка и инициализировать ее в реализации, как показано здесь: Статическая постоянная строка C ++ (член класса)
Однако это решение не работает для меня, потому что оно нарушает инкапсуляцию, которую я пытаюсь достичь с помощью идиомы pimpl.
Вопрос
Как я могу скрыть нецелую статическую переменную const внутри скрытого внутреннего класса при использовании идиомы pimpl?
Пример
Вот простейший (неверный) пример, который я мог придумать, демонстрирующий проблему:
Widget.h:
#ifndef WIDGET_H_
#define WIDGET_H_
#include <memory>
class Widget {
public:
Widget();
~Widget();
private:
class Impl;
std::unique_ptr<Impl> pimpl;
};
#endif
Widget.cpp:
#include "Widget.h"
#include <string>
class Widget::Impl {
public:
static const std::string TEST = "test";
Impl() { };
~Impl() { };
};
Widget::Widget() : pimpl(new Impl()) { }
Widget::~Widget() { }
Команда компиляции:
g++ -std=c++11 -Wall -c -o Widget.o ./Widget.cpp
Обратите внимание, что этот пример не может быть скомпилирован, потому что переменная TEST не может быть назначена при объявлении из-за того, что она не является интегральным типом; однако, поскольку он статичен, это необходимо. Похоже, это означает, что это невозможно.
Я искал предыдущие вопросы / ответы на это весь день, но не смог найти ни одного, предлагающего решение, которое сохраняет свойство скрытия информации идиомы pimpl.
Наблюдения за решением:
В моем примере выше я пытался присвоить значение TEST в объявлении класса Impl, которое находится внутри Widget.cpp, а не в его собственном файле заголовка. Определение Impl также содержится в Widget.cpp, и я считаю, что это было источником моего замешательства.
Просто переместив назначение TEST за пределы объявления Impl (но все еще в пределах определения Widget / Impl), проблема, похоже, решена.
В обоих приведенных ниже примерах решений TEST можно получить из Widget с помощью
pimpl->TEST
Попытка назначить другую строку в ТЕСТ, т.е.
pimpl->TEST = "changed"
приводит к ошибке компилятора (как и должно быть). Кроме того, попытка доступа к pimpl-> TEST извне Widget также приводит к ошибке компилятора, поскольку pimpl объявлен закрытым для Widget.
Итак, теперь TEST - это постоянная строка, к которой может получить доступ только Widget, она не названа в общедоступном заголовке, и одна копия совместно используется всеми экземплярами Widget точно так, как нужно.
Пример решения (char *):
В случае использования char * обратите внимание на добавление другого ключевого слова const; это было необходимо для предотвращения изменения TEST, чтобы он указывал на другой строковый литерал.
Widget.cpp:
#include "Widget.h"
#include <stdio.h>
class Widget::Impl {
public:
static const char *const TEST;
Impl() { };
~Impl() { };
};
const char *const (Widget::Impl::TEST) = "test";
Widget::Widget() : pimpl(new Widget::Impl()) { }
Widget::~Widget() { }
Пример решения (строка):
Widget.cpp:
#include "Widget.h"
#include <string>
class Widget::Impl {
public:
static const std::string TEST;
Impl() { };
~Impl() { };
};
const std::string Widget::Impl::TEST = "test";
Widget::Widget() : pimpl(new Widget::Impl()) { }
Widget::~Widget() { }
Обновлять:
Теперь я понимаю, что решение этой проблемы совершенно не связано с идиомой pimpl и представляет собой стандартный способ определения статических констант в C ++. Я привык к другим языкам, таким как Java, где константы нужно определять в момент их объявления, поэтому моя неопытность в C ++ не позволила мне понять это изначально. Я надеюсь, что это позволит избежать путаницы по двум темам.