Как создать частную статическую константную строку при использовании идиомы pimpl
Фон
Я изучал, как реализовать идиому pimpl, используя более новый метод C++11, описанный Хербом Саттером на этой странице: https://herbsutter.com/gotw/_100/
Я пытаюсь изменить этот пример, добавив переменную-член к частной реализации, в частности, std::string (хотя у char* такая же проблема).
проблема
Это кажется невозможным из-за использования статического константного нецелого типа. Инициализация в классе может быть выполнена только для целочисленных типов, но, поскольку она является статической, ее также нельзя инициализировать в конструкторе.
Решением этой проблемы является объявление закрытой переменной в заголовочном файле и инициализация ее в реализации, как показано здесь: C++ static static string (член класса)
Тем не менее, это решение не работает для меня, потому что оно нарушает инкапсуляцию, которую я пытаюсь достичь с помощью языка 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 можно получить из виджета, используя
pimpl->TEST
Попытка назначить другую строку в TEST, т.е.
pimpl->TEST = "changed"
приводит к ошибке компилятора (как и должно быть). Кроме того, попытка доступа к pimpl->TEST извне Widget также приводит к ошибке компилятора, поскольку pimpl объявлен закрытым для Widget.
Таким образом, теперь TEST является константной строкой, доступ к которой возможен только с помощью виджета, она не названа в общедоступном заголовке, и единственная копия является общей для всех экземпляров виджета, точно так, как требуется.
Пример решения (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++ помешала мне понять это изначально. Я надеюсь, что это поможет избежать путаницы в двух темах.
2 ответа
#include <memory>
class Widget {
public:
Widget();
~Widget();
private:
class Impl;
std::unique_ptr<Impl> pimpl;
};
/*** cpp ***/
#include <string>
class Widget::Impl {
public:
static const std::string TEST;
Impl() { };
~Impl() { };
};
const std::string Widget::Impl::TEST = "test";
Widget::Widget() : pimpl(new Impl()) { }
Widget::~Widget() { }
Вы можете рассмотреть вопрос о создании TEST
статическая функция, которая возвращает const std::string&
, Это позволит вам определить его в строке.
Вы также можете заменить const
от constexpr
в вашем примере, и он будет компилироваться.
class Widget::Impl {
public:
static constexpr std::string TEST = "test"; // constexpr here
Impl() { };
~Impl() { };
};
Обновить:
Что ж, похоже, я ошибался... Я всегда храню необработанные строки, когда мне нужны константы.
class Widget::Impl {
public:
static constexpr char * const TEST = "test";
};
В зависимости от модели использования, это может быть уместным или нет. Если нет, то определите переменную, как описано в другом ответе.