Могу ли я преобразовать объект и получить доступ к закрытым членам данных в C ++? - PullRequest
7 голосов
/ 03 октября 2009

Я хочу получить доступ к личному члену данных в классе. В классе нет функции-члена для доступа к частному члену данных. Это личное.

Я хочу взять урок и кое-как взломать его. Один из методов состоял в том, чтобы скопировать объявление класса, сделать закрытый член общедоступным и вызвать новый класс classthing_else. Затем я переинтерпретирую приведение и копирую оригинальный объект. Это работает. Но я хочу что-то более элегантное ... или, возможно, общее ... или просто другой способ.

Какие есть варианты? Могу ли я использовать void *? Могу ли я записать класс в другой пустой класс? Какие способы сделать это ??

%

Ответы [ 6 ]

18 голосов
/ 03 октября 2009

Я предполагаю, что

  1. Вы уже прошли этап "нарушение инкапсуляции - это плохо",
  2. Исчерпаны другие возможные решения,
  3. Невозможно изменить заголовок класса.

Существует несколько способов нарушить доступ к закрытым членам класса, как показано в GotW # 76 .

  1. Дублируйте определение класса и добавьте объявление friend.
  2. Используйте злые макросы: #define private public перед включением заголовка класса .
  3. Напишите определение класса с идентичным двоичным макетом и используйте reinterpret_cast для переключения с исходного класса на поддельный.
  4. Укажите функцию-член шаблона, если она есть (единственное переносимое решение).
3 голосов
/ 03 октября 2009

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

#include <class.h>
ObjectFoo instance;

Возможно (но опасно, если вы не осторожны) изменить заголовок (a) или скопировать заголовок в другое место и включить этот заголовок (b), без перекомпиляции класса сама .

#include <class_fixed.h>
ObjectFoo instance;

Ваш код, в который вы включили новый заголовок, просто подумает , что в объектном файле (который вы не перекомпилировали!) Он найдет реализацию класса, объявленного как в class_fixed.h. Пока сохраняется класс, объявленный как в class.h. Если вы измените смещения членов (например, добавьте новых участников) в своем новом заголовке, вы мертвы и код не будет работать должным образом. Но просто изменение доступа работает нормально. Скомпилированный код не знает о доступе, это важно только при компиляции.

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


Есть несколько решений.

  1. memcpy()
    Не! Не используйте memcpy, поскольку копирование объектов иногда подвергается определенной политике, навязанной разработчиком класса. Например, auto_ptr s нельзя просто скопировать в память: если вы запечатлите auto_ptr и запустите деструктор для обоих, вы попытаетесь освободить одну и ту же память два раза, и программа авария.

  2. Измените private: на public: в заголовке или с помощью макроса
    Если ваша лицензия это позволяет, вы можете решить вашу проблему, отредактировав файл заголовка, который поставляется с реализацией класса . Независимо от того, находится ли исходный код реализации (т.е. cpp-файл класса) под вашим контролем, не имеет значения: достаточно изменить private на public для членов данных (в заголовке) и работает просто отлично, даже если вы получили бинарный единственная библиотека, которая содержит определение класса. (Для функций-членов изменение доступа иногда меняет свое внутреннее имя, но для MSVS и GCC это нормально.)

  3. Добавление новой функции получения
    При изменении private на public почти всегда все в порядке (если только вы не полагаетесь на определенные проверки во время компиляции, которые должны прервать компиляцию, если в классе есть определенный доступный член), добавление новой функции получения должно выполняться аккуратно. Функция получения должна быть встроенной (и поэтому определена в заголовочном файле класса).

  4. reinterpret_cast
    Приведение работает отлично , если вы НЕ приводите указатель на динамический базовый класс (динамическое средство "с виртуальными функциями или базами) ") чей фактический экземпляр на момент приведения может быть получен из класса по конкретному коду.

  5. protected:
    И на всякий случай, если вы забыли. C ++ может объявлять члены protected:, то есть доступные только для классов, производных от заданных. Это может удовлетворить ваши потребности.

3 голосов
/ 03 октября 2009

С идеей, которую вы предлагаете в своем вопросе, вам не нужно копировать оригинальный объект. Если вы напишите свой собственный «общедоступный» вариант объявления реального класса, а затем приведете указатель на этот новый тип, вы сможете напрямую обращаться к объекту через него.

Причина, по которой все это не является хорошей идеей, проста. Вы должны манипулировать объектами класса, источником которого вы не управляете (в противном случае вы сможете изменить источник, чтобы предоставить вам необходимый доступ). Но если вы не контролируете источник, то что, если сопровождающие изменят макет своего класса? Ваша дублированная версия больше не будет совпадать, и компилятор не сможет обнаружить это несоответствие. Результатом, вероятно, будет повреждение памяти во время выполнения.

1 голос
/ 03 октября 2009

Вы можете, но не должны. Объекты - это просто память. Вы, безусловно, можете привести указатель в эквивалентный класс, который имеет те же члены, но где все общедоступно. Но почему вы хотите это сделать? У вас есть чужой код, с которым вам нужно работать? Получите их, чтобы добавить надлежащие методы доступа. Вы действительно должны относиться к ним как к публичным участникам? Изменить класс.

Я не совсем уверен, что вы пытаетесь сделать, но это, вероятно, ошибка.

0 голосов
/ 03 октября 2009

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


#include<iostream>
using namespace std;

// Class Objectfoo
// Pretend Objectfoo lives somewhere else ... I cannot open him up

class ObjectFoo 
{
  private:
  int datax; 
  public:
   ObjectFoo() { datax = 100; }
   void get() { cout << datax << endl;}
};

// Class ObjectBar
class ObjectBar 
{
  public:
   int datax;
};

ObjectFoo FOOEY;

ObjectBar* touch_foo(int x, ObjectFoo* foo , ObjectBar* bar)
{
 bar = reinterpret_cast<ObjectBar*>(foo);
 bar->datax = x;
 return bar;
}

int main() 
{
  ObjectBar* bar;

  cout << "Displaying private member in ObjectFoo i.e. ObjectFoo.datax" << endl;
  FOOEY.get();

  cout << "Changing private member " << endl;
  bar = touch_foo(5, &FOOEY, bar);

  cout << "bar->datax = " << bar->datax << endl;

  cout << "Displaying private member in ObjectFoo i.e. ObjectFoo.datax" << endl;
  FOOEY.get();

  return 0;
}

Это работает ... но я думаю, что хочу что-то более общее ... или более гибкое.

%

0 голосов
/ 03 октября 2009

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

У вас должно быть объявление класса, поэтому у вас, вероятно, есть заголовок, но, возможно, нет файла .cpp / what. Добавьте встроенную функцию-член в класс в копии заголовка и включите этот заголовок вместо оригинала. Вы по-прежнему сможете ссылаться на объектный файл для недоступного исходного кода.

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

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

...