Является ли 'this' гарантией указать на начало объекта в С++?

Я хочу записать объект в последовательный файл, используя fwrite. Класс похож на

class A{
    int a;
    int b;
public:
    //interface
}

и когда я пишу объект в файл. Я блуждаю, что могу использовать fwrite( this, sizeof(int), 2, fo) для записи первых двух целых чисел.

Вопрос: this гарантированно указывает на начало объекта данных, даже если в самом начале объекта существует виртуальная таблица. Таким образом, приведенная выше операция безопасна.

Ответ 1

this предоставляет адрес объекта, который не обязательно является адресом первого элемента. Единственным исключением являются так называемые стандартные макеты. Из стандарта С++ 11:

(9.2/20) Указатель на объект структуры стандартного макета, подходящим образом преобразованный с помощью reinterpret_cast, указывает на его начальный элемент (или если этот элемент является битовым полем, то к единице, в которой он находится ) и наоборот. [Примечание. Таким образом, в рамках объекта структуры стандартного макета может быть указано неназванное заполнение, но не в его начале, по мере необходимости, для достижения соответствующего выравнивания. - конечная нота]

Это определение типа стандартного макета:

(9/7) Класс стандартного макета - это класс, который:
- не имеет нестатических элементов данных типа нестандартного макета (или массива таких типов) или ссылки,
- не имеет виртуальных функций (10.3) и нет виртуальных базовых классов (10.1),
- имеет тот же контроль доступа (раздел 11) для всех нестатических элементов данных,
- не имеет базовых классов нестандартной компоновки,
- либо не имеет нестатических членов данных в самом производном классе и не более одного базового класса с нестатическими членами данных, либо не имеет базовых классов с нестатическими членами данных и
- не имеет базовых классов того же типа, что и первый нестатический элемент данных. [108]

[108] Это гарантирует, что два подобъекта, которые имеют один и тот же тип класса и принадлежат одному и тому же самому производному объекту, не выделяются по тому же адресу (5.10).

Обратите внимание, что тип объекта не должен быть POD – достаточно стандартного макета, как определено выше. (Все POD имеют стандартную компоновку, но, кроме того, они тривиально конструктивные, тривиально подвижные и тривиально скопируемые.)

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

(9/9) [Примечание. Стандартные классы макета полезны для общения с кодом, написанным на других языках программирования. Их макет указан в 9.2. - конечная нота]

Конечно, это не решает все проблемы сериализации. В частности, вы не получите переносимости сериализованных данных (например, из-за несовместимости со знаком).

Ответ 2

Нет, нет. Вы можете использовать fwrite(&a, sizeof(int), 2, fo), но вы тоже не должны. Просто прогулка по необработанной памяти редко является хорошей идеей, когда дело доходит до безопасности, потому что вы не должны полагаться на конкретные макеты памяти. Кто-то может ввести другую переменную c между a и b, не заметив, что он нарушает ваш код. Если вы хотите получить доступ к своим переменным, сделайте это явно. Не просто получить доступ к памяти, где, по вашему мнению, находятся переменные или где они были в последний раз, когда вы проверяли.

Ответ 3

Многие ответы правильно сказали "Нет". Вот какой код, который показывает, почему this никогда не будет гарантированно указывать на начало объекта:

#include <iostream>

class A {
    public: virtual int value1() { std::cout << this << "\n"; }
};

class B {
    public: virtual int value2() { std::cout << this << "\n"; }
};

class C : public A, public B {};

int main(int argc, char** argv) {
    C* c = new C();
    A* a = (A*) c;
    B* b = (B*) c;
    a->value1();
    b->value2();
    return 0;
}

Обратите внимание на использование this в виртуальных методах.

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

Ответ 4

Запись объекта в файл с помощью fwrite - очень плохая идея по многим причинам. Например, если ваш класс содержит std::vector<int>, вы будете сохранять указатели на целые числа, а не целые числа.

Для "более высокого уровня" (выравнивание, управление версиями, двоичная совместимость) это также плохая идея в большинстве случаев даже в C и даже когда члены являются просто простыми родными типами.