Как создать частную статическую константную строку при использовании идиомы 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";
};

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

Другие вопросы по тегам