Обход проблемы с константной корректностью внешней библиотеки

Я использую внешнюю библиотеку C ++, в которой отсутствует константная корректность. Допустим, я работаю с объектами следующего класса:

// Library.h
namespace Library {

class Message {
 public:
    std::string getData() {
        return data_;
    }
 private:
    std::string data_;
};

}  // namespace Library

Обратите внимание, что getData() возвращает копию, поэтому вызов метода не изменяет объект Message, и он должен быть const. Однако производитель решил, что это не так. С моей стороны кода важна константная корректность, и Message должен использоваться в такой функции:

// MyApplication.cpp

template<class T>
void handleMessage(const T& msg) {
    std::string content = msg.getData();
    // interprete and process content ...
}

Есть ли способ добиться этого? Другими словами, как обойти ошибку error: passing 'const Library::Message' as 'this' argument discards qualifiers без изменения сигнатуры функции handleMessage?


person jotrocken    schedule 14.09.2017    source источник
comment
Вы можете использовать const_cast в крайнем случае.   -  person user0042    schedule 14.09.2017
comment
Обратите внимание, что удаление const через const_cast в вашей ссылке вызовет неопределенное поведение, если выполняются два условия: (1) исходный конкретный объект фактически был const, и (2) объект был изменен. Он не вызывает неопределенное поведение, если любой из них false (как ваш ... на данный момент). В любом случае, это возлагает огромную ответственность на кодировщика, поскольку очень легко намеренно кодировать что-то, что в то время казалось безопасным, только позже, чтобы обнаружить, что кто-то добавил использование, которое передает const объект; компилятор не успел его поймать.   -  person WhozCraig    schedule 14.09.2017


Ответы (2)


Вы также можете использовать оболочку с изменяемой переменной-членом, например:

#include <string>

class Message {
 public:
    std::string getData() {
        return data_;
    }
    Message(std::string data): data_{data} { }
 private:
    std::string data_;
};

class MessageWrapper {
 public:
    MessageWrapper(Message message): message{message} {}
    std::string getData() const {
        return message.getData();
    }
 private:
    mutable Message message;
};

template<class T>
void handleMessage(const T& msg) {
    std::string content = msg.getData();

}

int main() {
    MessageWrapper mw{{"abc"}};
    handleMessage(mw);
}

[живая демонстрация]

Изменить:

Чтобы обеспечить правильность констант, вы можете сохранить однажды полученные данные из сообщения, например:

#include <string>
#include <optional>

class Message {
 public:
    std::string getData() {
        return data_;
    }
    Message(std::string data): data_{data} { }
 private:
    std::string data_;
};

class MessageWrapper {
 public:
    MessageWrapper(Message message): message{message} {}
    std::string getData() const {
        return (data)?(*data):(*(data = message.getData()));
    }
 private:
    mutable Message message;
    mutable std::optional<std::string> data;
};

template<class T>
void handleMessage(const T& msg) {
    std::string content = msg.getData();

}

int main() {
    MessageWrapper mw{{"abc"}};
    handleMessage(mw);
}

[живая демонстрация]

person W.F.    schedule 14.09.2017
comment
тоже вариант, но mutable тоже читерство константности - person 463035818_is_not_a_number; 14.09.2017
comment
Это должен быть канонический способ исправления. - person Passer By; 14.09.2017
comment
@ tobi303 это не так, поскольку message является изменяемым, поэтому его можно изменять все время, когда вы используете переменную mw. - person W.F.; 14.09.2017
comment
Я не говорю, что с этим что-то не так, но вы все равно вызываете неконстантный метод для чего-то, что кажется константным. Если библиотека когда-либо решит действительно изменить наблюдаемое состояние в этом методе, клиентский код может сломаться, я не думаю, что есть какой-либо способ полностью избежать этого. - person 463035818_is_not_a_number; 14.09.2017
comment
@ tobi303 о, да, в этом смысле вы правы ... не гарантируется получение данных с одинаковым значением при каждом вызове, если библиотека не гарантирует, что ... - person W.F.; 14.09.2017
comment
правильный способ гарантировать, что метод будет объявлен как const;). Неважно, ты уже получил мой голос, и имхо твой ответ лучше, чем мой - person 463035818_is_not_a_number; 14.09.2017
comment
mutable - это явно путь вперед с макросом. Он не только защищает вас от фактического изменения объекта getData (), но также дает вам удобный простой способ удалить изменяемый объект, когда / если библиотека исправлена. - person UKMonkey; 14.09.2017

В основном у вас есть два варианта. Вы можете использовать const_cast

template<class T>
void handleMessage(const T& msg) {
    std::string content = const_cast<T&>(msg).getData();
    // interprete and process content ...
}

это нормально, если вы точно знаете, что getData на самом деле не изменяет никакие члены. Или, если вы не возражаете против накладных расходов, вы можете сделать копию:

template<class T>
void handleMessage(const T& msg) {
    T copy = msg;
    std::string content = copy.getData();
    // interprete and process content ...
}

... или в качестве третьего варианта, если вы хотите скрыть const_cast с сайта вызова, вы можете обернуть его:

class MyMessage {
     Message msg;
public:
     std::string getData() const {
         return const_cast<Message>(msg).getData();
     }
};
person 463035818_is_not_a_number    schedule 14.09.2017
comment
Библиотека getData() уже создает копию, поэтому накладные расходы на ее сохранение в строке минимальны. - person jotrocken; 15.09.2017
comment
@jotrocken ну это дополнительная копия поверх копии, которую делает getData() - person 463035818_is_not_a_number; 15.09.2017
comment
да, но хотя бы один из них будет оптимизирован компилятором, надеюсь - person jotrocken; 15.09.2017