У меня есть следующий класс:
class BritneySpears
{
public:
int getValue() { return m_value; };
private:
int m_value;
};
Который является внешней библиотекой (что я не могу измениться). Я, очевидно, не могу изменить значение m_value
, только считайте его. Даже получение из BritneySpears
не будет работать.
Что, если я определяю следующий класс:
class AshtonKutcher
{
public:
int getValue() { return m_value; };
public:
int m_value;
};
И затем сделайте:
BritneySpears b;
// Here comes the ugly hack
AshtonKutcher* a = reinterpret_cast<AshtonKutcher*>(&b);
a->m_value = 17;
// Print out the value
std::cout << b.getValue() << std::endl;
Я знаю, что это - плохая практика. Но только из любопытства: это, как гарантируют, будет работать? Это - определенное поведение?
Вопрос о премии: необходимо ли было когда-либо использовать такой ужасный взлом?
Это неопределенное поведение. Члены в каждой секции access-квалификатора гарантированно располагаются в порядке их появления, но между acccess-квалификаторами такой гарантии нет. Например, если компилятор решит расположить все приватные члены перед всеми публичными членами, то два вышеприведенных класса будут иметь разное расположение.
Edit: Пересмотрев этот старый ответ, я понял, что упустил довольно очевидный момент: определения struct имеют ровно по одному члену данных. Порядок функций-членов не имеет значения, поскольку они не вносят вклад в компоновку класса. Вы вполне можете обнаружить, что оба члена данных гарантированно находятся в одном и том же месте, хотя я не знаю стандарт достаточно хорошо, чтобы сказать наверняка.
Но! Вы не можете разыменовать результат reinterpret_cast
инга между несвязанными типами. Это все еще UB. По крайней мере, таково мое прочтение http://en.cppreference.com/w/cpp/language/reinterpret_cast, а это действительно ужасное прочтение.
Это неопределенное поведение, по причинам, указанным Марсело. Но иногда приходится прибегать к таким вещам при интеграции внешнего кода, который вы не можете модифицировать. Более простой способ сделать это (и такое же неопределенное поведение):
#define private public
#include "BritneySpears.h"
Возможно, вы не сможете изменить библиотеку для BritneySpears
, но вы должны быть в состоянии изменить заголовочный файл .h. Если это так, вы можете сделать AshtonKutcher
другом BritneySpears
:
class BritneySpears
{
friend class AshtonKutcher;
public:
int getValue() { return m_value; };
private:
int m_value;
};
class AshtonKutcher
{
public:
int getValue(const BritneySpears & ref) { return ref.m_value; };
};
Я не могу одобрить этот трюк, и не думаю, что когда-либо пробовал его сам, но это должно быть законным хорошо определенным C++.
@Marcelo прав: порядок членов не определен на разных уровнях доступа.
Но рассмотрим следующий код; здесь AshtonKutcher
имеет точно такую же структуру, что и BritneySpears
:
class AshtonKutcher
{
public:
int getValue() { return m_value; };
friend void setValue(AshtonKutcher&, int);
private:
int m_value;
};
void setValue(AshtonKutcher& ac, int value) {
ac.m_Value = value;
}
Я считаю, что это действительно может быть действительным C ++.
В вашем коде есть проблема, подчеркнутая ответами. Проблема возникает из-за упорядочивания значений.
Однако вы почти угадали:
class AshtonKutcher
{
public:
int getValue() const { return m_value; }
int& getValue() { return m_value; }
private:
int m_value;
};
Теперь у вас точно такая же схема, потому что у вас одинаковые атрибуты, объявленные в одинаковом порядке и с одинаковыми правами доступа... и ни у одного из объектов нет виртуальной таблицы.
Таким образом, хитрость заключается не в изменении уровня доступа, а в добавлении метода :)
Если, конечно, я ничего не упустил.
Я точно сказал, что это кошмар в плане обслуживания?
Использования reinterpret_cast обычно следует избегать, и оно не гарантирует переносимых результатов.
Кроме того, почему вы хотите изменить частный член? Вы могли бы просто обернуть исходный класс в новый (предпочитаю композицию наследованию) и обработать метод getValue так, как вам нужно.