C ++ - Изменить приватный член вне класса

0

Является ли этот код причиной неопределенного поведения? Или я могу столкнуться с проблемой с этим? (скопируйте полный класс без функций, просто переменные с публичным модификатором и измените личные memebers, чтобы этот указатель):

#include <iostream>

using namespace std;

class Point {

private:

    int x;
    int y;

public:

    Point(int x, int y) {
        this->x = x;
        this->y = y;
    }

    void Print() {
        cout << "(" << x << ", " << y << ")" << endl;
    }

};

struct PointHack {

    int x;
    int y;

};

int main() {
    Point a(4, 5);
    a.Print();
    ((PointHack *) (&a))->x = 1;
    ((PointHack *) (&a))->y = 2;
    a.Print();

    return 0;
}

вывод:

(4, 5)
(1, 2)

(конечно, с исходным заказом участника)

Теги:

3 ответа

3
Лучший ответ

Несмотря на то, что ваши классы совместимы с макетами (см. Ниже), ваш код демонстрирует неопределенное поведение из-за того, что такие приведения указателей запрещены правилами C++ строгого сглаживания 1.

Но: заменяя броски на union делает код стандартным; на самом деле это гарантировано в работе C++ 11:

#include <iostream>

using namespace std;

class Point {

private:

    int x;
    int y;

public:

    Point(int x, int y) {
        this->x = x;
        this->y = y;
    }

    void Print() {
        cout << "(" << x << ", " << y << ")" << endl;
    }

};

struct PointHack {
    int x;
    int y;
};

union pu
{
    Point p;
    PointHack ph;
    pu(int x, int y) : p(x, y) {}
};

int main() {
    pu u(4,5);
    u.p.Print();
    u.ph.x=1;
    u.ph.y=2;
    u.p.Print();
    return 0;
}

Это происходит из-за того, что Point и PointHack являются PointHack стандартного макета 2 (C++ 11, §9 ¶7) и разделяют "общую начальную подпоследовательность" (§9.2, ¶20); как таковые, если они оба хранятся в одном объединении (здесь pu), ему разрешено "проверять общую начальную часть любого из них" 3.

Тем не менее, этот ответ в основном представляет собой упражнение стиля; не используйте такие трюки, если вы действительно не вынуждены. C++ предоставляет более эффективные средства доступа к частным членам, если это необходимо, без жестокого нарушения инкапсуляции - у вас есть геттеры/сеттеры, защищенные наследования, классы друзей... И вообще, если вы получаете доступ к членам частного класса способами, не предназначенными для вашей цели класса, вы потенциально нарушаете предположения этого класса о том, как его данные изменяются, что может привести к неустойчивому поведению кода его методов.


Заметки:

  1. В C++ у вас не может быть двух указателей несвязанных типов, указывающих на один и тот же объект; это ограничение в основном используется, чтобы помочь оптимизаторам с предположениями об aliasing.
  2. Обратите внимание, что требования к этому довольно жесткие; обычно большинство классов, которые не являются в основном структурами C, не подходят для этого. Даже наличие разных квалификаторов доступа может нарушить магию.
  3. Идея состоит в том, что присвоение ph делает ее "активным объектом" union, а затем p.Print() является "проверкой" "неактивного" объекта.
1

Да, выложенный код вызывает неопределенное поведение. Не делай этого.

Если вы хотите увидеть по-настоящему безумный способ, которым некоторые люди справляются с этой сомнительной целью, здесь вы идете: получить доступ к частному члену, используя шаблонный трюк

Но на самом деле, не делай этого.

PS: никогда не используйте C-style cast в C++. Используйте static_cast, dynamic_cast, reinterpret_cast и const_cast, если это необходимо. C-style casts бесполезно небезопасны.

  • 0
    Но почему? Два класса не имеет одинаковую карту памяти?
  • 1
    @fred: проблема не в макете памяти (который действительно гарантированно будет таким же, поскольку они являются стандартными классами макетов, как в § 9-7), но выполнение такого рода приведения указателей нарушает требования C ++ без сглаживания.
Показать ещё 1 комментарий
0

я нашел проблему

/*code:*/
#include <stdio.h>

void check(short *h,long *k)
{
    *h=5;
    *k=6;
    if (*h == 5)
        printf("strict aliasing problem\n");
}

int main(void)
{
    long      k[1];
    check((short *)k,k);
    return 0;
}
/*
$ gcc -Wall -ansi -pedantic -O0 -o o0 asd.c
$ ./o0
/output: nothing/
$ gcc -Wall -ansi -pedantic -O2 -o o2 asd.c
$ ./o2
/output: strict aliasing problem/
*/

(так же, как в c++)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню