Подтвердить что ты не робот

Частный член взломал определенное поведение?

У меня есть следующий класс:

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;

Я знаю, что это плохой. Но просто из любопытства: гарантировано ли это? Определено ли это поведение?

Бонусный вопрос: вам когда-нибудь приходилось использовать такой уродливый хак?

Изменить: просто чтобы напугать меньше людей: я не собираюсь на самом деле делать это в реальном коде. Мне просто интересно;)

4b9b3361

Ответ 1

Это поведение undefined. Члены в каждом разделе классификатора доступа должны быть выложены в том порядке, в котором они появляются, но нет никакой гарантии между квалификаторами квалификации. Например, если компилятор решит разместить всех частных членов перед всеми публичными членами, эти два класса будут иметь разный макет.

Изменить: Повторяя этот старый ответ, я понял, что пропустил довольно очевидный момент: определения структуры имеют ровно один член данных. Порядок функций-членов не имеет значения, поскольку они не вносят вклад в макет класса. Вы вполне можете найти, что оба элемента данных гарантированно находятся в одном и том же месте, хотя я не знаю, что стандарт достаточно хорошо, чтобы сказать наверняка.

Но! Вы не можете разыгрывать результат reinterpret_cast между несвязанными типами. Это все еще UB. По крайней мере, это мое чтение http://en.cppreference.com/w/cpp/language/reinterpret_cast, которое действительно является gnarly.

Ответ 2

Это поведение undefined, по причинам Marcelo. Но иногда вам нужно прибегать к таким вещам при интеграции внешнего кода, который вы не можете изменить. Более простой способ сделать это (а также поведение undefined):

#define private public
#include "BritneySpears.h"

Ответ 3

Возможно, вы не сможете изменить библиотеку для 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; }; 
}; 

Я не могу потворствовать этому трюку, и я не думаю, что я когда-либо пробовал это сам, но это должен быть законный четко определенный С++.

Ответ 4

@Marcelo имеет право: порядок членов undefined на разных уровнях доступа.

Но рассмотрим следующий код; здесь 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;
}

Я считаю, что это может быть действительно С++.

Ответ 5

Существует проблема с вашим кодом, подчеркнутая ответами. Проблема связана с упорядочением значений.

Однако вы были почти там:

class AshtonKutcher
{
public:

  int getValue() const { return m_value; }
  int& getValue() { return m_value; }

private:
  int m_value;
};

Теперь у вас есть тот же самый макет, потому что у вас есть те же атрибуты, объявленные в том же порядке и с теми же правами доступа... и ни у одного объекта нет виртуальной таблицы.

Таким образом, трюк не должен изменить уровень доступа, но добавить метод:)

Если, конечно, я что-то пропустил.

Я точно уточнил, что это кошмар для обслуживания?

Ответ 6

Использовать reinterpret_cast обычно следует избегать, и он не гарантирует, что переносит результаты.

Кроме того, почему вы хотите изменить частного пользователя? Вы можете просто обернуть исходный класс в новый (предпочитаете состав над наследованием) и обрабатывать метод getValue по своему усмотрению.