Частный член взламывает определенное поведение? - PullRequest
22 голосов
/ 14 мая 2010

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

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;

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

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

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

Ответы [ 6 ]

21 голосов
/ 14 мая 2010

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

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

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

9 голосов
/ 14 мая 2010

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

#define private public
#include "BritneySpears.h"
4 голосов
/ 14 мая 2010

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

2 голосов
/ 14 мая 2010

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

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

class AshtonKutcher
{
public:

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

private:
  int m_value;
};

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

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

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

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

2 голосов
/ 14 мая 2010

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

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

1 голос
/ 14 мая 2010

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

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

...