Могу ли я получить доступ к закрытым членам вне класса, не используя друзей? - PullRequest
62 голосов
/ 08 января 2009

Отказ

Да, я полностью осознаю, что то, о чем я спрашиваю, совершенно глупо, и что любой, кто хотел бы попробовать такое в рабочем коде, должен быть уволен и / или застрелен. В основном я смотрю, можно ли сделать .

Теперь, когда это не так, есть ли способ получить доступ к закрытым членам класса в C ++ извне класса? Например, есть ли способ сделать это со смещением указателя?

(приветствуются наивные и не готовые к производству техники)

Обновление

Как отмечалось в комментариях, я задал этот вопрос, потому что хотел написать пост в блоге о чрезмерной инкапсуляции (и как это влияет на TDD). Я хотел посмотреть, есть ли способ сказать: «Использование частных переменных не является на 100% надежным способом обеспечения инкапсуляции, даже в C ++». В конце я решил больше сосредоточиться на том, как решить проблему, а не на том, почему это проблема, поэтому я не представил некоторые материалы, поднятые здесь, так заметно, как планировал, но я все же оставил ссылку.

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

Ответы [ 24 ]

67 голосов
/ 08 января 2009

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

safe.h

class safe
{
    int money;

public:
    safe()
     : money(1000000)
    {
    }

    template <typename T>
    void backdoor()
    {
        // Do some stuff.
    }
};

main.cpp:

#include <safe.h>
#include <iostream>

class key;

template <>
void safe::backdoor<key>()
{
    // My specialization.
    money -= 100000;
    std::cout << money << "\n";
}

int main()
{
    safe s;
    s.backdoor<key>();
    s.backdoor<key>();
}

Выход:

900000
800000
51 голосов
/ 04 июля 2010

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

struct A {
private:
  int member;
};

Просто объявите для него структуру, в которой вы ее описываете, и создайте экземпляр класса реализации, используемого для ограбления

// tag used to access A::member
struct A_member { 
  typedef int A::*type;
  friend type get(A_member);
};

template struct Rob<A_member, &A::member>;

int main() {
  A a;
  a.*get(A_member()) = 42; // write 42 to it
  std::cout << "proof: " << a.*get(A_member()) << std::endl;
}

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

template<typename Tag, typename Tag::type M>
struct Rob { 
  friend typename Tag::type get(Tag) {
    return M;
  }
};

Однако это не означает, что правила доступа в c ++ ненадежны. Правила языка разработаны для защиты от случайных ошибок - если вы пытаетесь ограбить данные объекта, язык by-design не займет много времени, чтобы вас предотвратить.

29 голосов
/ 08 января 2009

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

#define private public
#define class struct

Но это ответ на ваш OP, в котором вы явно предлагаете технику, которая, и я цитирую, «абсолютно глупа, и что любой, кто хотел бы попробовать такую ​​вещь в рабочем коде, должен быть уволен и / или застрелен ».


Другой метод заключается в получении доступа к частным данным члена путем создания указателей с использованием жестко закодированных / закодированных вручную смещений от начала объекта.

24 голосов
/ 08 января 2009

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

Ожидаются искры и, возможно, сбой;)

12 голосов
/ 09 января 2009
class A 
{ 
   int a; 
}
class B
{
   public: 
   int b;
}

union 
{ 
    A a; 
    B b; 
};

Это должно сделать это.

ETA: Это будет работать для такого рода тривиального класса, но в целом это не так.

TC ++ PL Раздел C.8.3: «Класс с конструктором, деструктором или операцией копирования не может быть типом члена объединения ... потому что компилятор не будет знать, какой член уничтожить.»

Таким образом, нам лучше всего объявить class B в соответствии с макетом A и взломать, чтобы посмотреть на рядовых.

9 голосов
/ 08 января 2009

Если вы можете получить указатель на член класса, вы можете использовать указатель независимо от того, какие спецификаторы доступа (даже методы).

class X;
typedef void (X::*METHOD)(int);

class X
{
    private:
       void test(int) {}
    public:
       METHOD getMethod() { return &X::test;}
};

int main()
{
     X      x;
     METHOD m = x.getMethod();

     X     y;
     (y.*m)(5);
}

Конечно, мой любимый маленький взломщик - задняя дверь шаблона друга.

class Z
{
    public:
        template<typename X>
        void backDoor(X const& p);
    private:
        int x;
        int y;
};

Предполагая, что создатель выше определил backDoor для его обычного использования. Но вы хотите получить доступ к объекту и посмотреть на закрытые переменные-члены. Даже если вышеуказанный класс был скомпилирован в статическую библиотеку, вы можете добавить свою собственную специализацию шаблона для backDoor и таким образом получить доступ к членам.

namespace
{
    // Make this inside an anonymous namespace so
    // that it does not clash with any real types.
    class Y{};
}
// Now do a template specialization for the method.
template<>
void Z::backDoor<Y>(Y const& p)
{
     // I now have access to the private members of Z
}

int main()
{
    Z  z;   // Your object Z

    // Use the Y object to carry the payload into the method.
    z.backDoor(Y());
}
8 голосов
/ 08 января 2009

Определенно возможно получить доступ к закрытым членам со смещением указателя в C ++. Предположим, у меня было следующее определение типа, к которому я хотел получить доступ.

class Bar {
  SomeOtherType _m1;
  int _m2;
};

Предполагается, что в Bar нет виртуальных методов. Простой случай - _m1. Члены в C ++ хранятся как смещения места в памяти объекта. Первый объект со смещением 0, второй объект со смещением sizeof (первый член) и т. Д.

Итак, вот способ доступа к _m1.

SomeOtherType& GetM1(Bar* pBar) {
  return*(reinterpret_cast<SomeOtherType*>(pBar)); 
}

Теперь _m2 немного сложнее. Нам нужно переместить исходный указатель sizeof (SomeOtherType) из оригинала. Приведение к char должно гарантировать, что я увеличиваю смещение байта

int& GetM2(Bar* pBar) {
  char* p = reinterpret_cast<char*>(pBar);
  p += sizeof(SomeOtherType);
  return *(reinterpret_cast<int*>(p));
}
4 голосов
/ 29 марта 2011

крутой вопрос, кстати ... вот мой кусок:

using namespace std;

class Test
{

private:

  int accessInt;
  string accessString;

public:

  Test(int accessInt,string accessString)
  {
    Test::accessInt=accessInt;
    Test::accessString=accessString;
  }
};

int main(int argnum,char **args)
{
  int x;
  string xyz;
  Test obj(1,"Shit... This works!");

  x=((int *)(&obj))[0];
  xyz=((string *)(&obj))[1];

  cout<<x<<endl<<xyz<<endl;
  return 0;
}

Надеюсь, это поможет.

4 голосов
/ 01 января 2017

Этот ответ основан на точной концепции, продемонстрированной @ ответом Йоханнеса / блогом , поскольку это, кажется, единственный «законный» способ. Я преобразовал этот пример кода в удобную утилиту. Он легко совместим с C ++ 03 (благодаря реализации std::remove_reference и замене nullptr).

Библиотека

#define CONCATE_(X, Y) X##Y
#define CONCATE(X, Y) CONCATE_(X, Y)

#define ALLOW_ACCESS(CLASS, TYPE, MEMBER) \
  template<typename Only, TYPE CLASS::*Member> \
  struct CONCATE(MEMBER, __LINE__) { friend TYPE (CLASS::*Access(Only*)) { return Member; } }; \
  template<typename> struct Only_##MEMBER; \
  template<> struct Only_##MEMBER<CLASS> { friend TYPE (CLASS::*Access(Only_##MEMBER<CLASS>*)); }; \
  template struct CONCATE(MEMBER, __LINE__)<Only_##MEMBER<CLASS>, &CLASS::MEMBER>

#define ACCESS(OBJECT, MEMBER) \  
(OBJECT).*Access((Only_##MEMBER<std::remove_reference<decltype(OBJECT)>::type>*)nullptr)

API

ALLOW_ACCESS(<class>, <type>, <member>);

Использование

ACCESS(<object>, <member>) = <value>;   // 1
auto& ref = ACCESS(<object>, <member>); // 2

Пример

struct X {
  int get_member () const { return member; };
private:
  int member = 0;
};

ALLOW_ACCESS(X, int, member);

int main() {
  X x;
  ACCESS(x, member) = 42;
  std::cout << "proof: " << x.get_member() << std::endl;
}
3 голосов
/ 08 января 2009

Если вы знаете, как ваш C ++ компилятор искажает имена, да.

Если, я полагаю, это виртуальная функция. Но тогда, если вы знаете, как ваш компилятор C ++ создает VTABLE ...

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

...