2014-09-28 1 views
2

Я делаю несколько экспериментов с подделкой ООП на C, и я наткнулся на головоломку. В C++ я предполагаю, что компилятор вставляет деструкторы в функцию epilogue после выполнения инструкции return.«Fake» OOP in C - как бороться с деструкторами и поддельными функциональными эпилогами при возвращении

Заблуждение, что на C потребует, чтобы деструкторы были вызваны вручную в соответствующем порядке, но проблема в том, что возвращаемое значение может зависеть от некоторых из этих объектов, поэтому с одной стороны уничтожение не может произойти до оператора возврата, с другой стороны после операторов возврата никогда не достигаются. И проблема усложняется тем фактом, что могут быть несколько операторов возврата из разных блоков, которые требуют собственных поддельных эпилогов.

Итак, вопрос в том, как я могу с этим справиться? Это не должно быть приятно, так как это не похоже на то, что это может быть ...

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

T foo() { 
    T _retValue; 
    ... 
    if (something) { 
     ... 
     _retValue = someValue; 
     goto blockID_cleanup; 

     blockID_cleanup: 
     ... 
     goto foo_cleanup; // goto parent block until function block 
    } 

    _retValue = somethingElse; 
    goto foo_cleanup; 

    foo_cleanup: 
    ... 
    return _retValue; 
} 
+6

Половина меня хочет знать * почему * вы пойдете на все эти неприятности, когда Бьярне уже сделал это за вас три десятилетия назад. –

+0

Его невозможно выполнить перед возвратом, и это не то, что делает C++, сгенерированный код просто вызывает dtors перед возвратом, как любая нормальная функция. – paulm

+4

@JohnZwinck - почему вы когда-нибудь научились испытывать трудности с поставкой штанов после того, как ваши родители сделали это за вас десять лет назад? ;) Что касается меня - мой интересующийся орган хочет знать. Это плохо? – 2014-09-28 10:32:27

ответ

1

В C++, значение, возвращаемое из функции не должны обращаться к памяти локальных объектов, что было бы ошибкой. Итак, для меня похоже, что вы, возможно, пытаетесь решить проблему, даже не решить компилятор C++.

Если вы хотите просто вернуть значение, вычисленное одним из локальных объектов, сначала присвойте значение, вычисленное объектом локальной переменной, разрушите объект и затем верните предварительно вычисленное возвращаемое значение.

+0

«n C++, значение, возвращаемое функцией, не должно ссылаться на память локальных объектов» - это звучит так, как будто вы подразумеваете ошибку «int i = a - b; return i; return указывает на память локальных объектов. Но то, что я имел в виду, говорил динамический массив и возвращал индекс этого массива, очевидно, вы не можете этого сделать, если деструктор массива уже выполнен и его память удалена. – 2014-09-28 10:51:55

+0

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

+1

@ user3735658 Оператор return может ссылаться на память локальных объектов. Утверждение 'return i' делает именно это. Однако возвращаемое значение является копией i. –

2

Edit: Кажется, что вы на самом деле спрашиваете, как объекты возвращаются из функций, ваш вопрос не 100% ясно, но здесь идет:

class A 
{ 
    public: 
    A(int value) 
    : mTest(value) {} 

    A operator + (const A& other) 
    { 
     return mTest + other.mTest; 
    } 

    operator int() 
    { 
     return mTest; 
    } 

    private: 
    int mTest = 0; 
}; 

int foo() 
{ 
    A a(2); 
    A aa(4); 
    return a + aa; 
} 

Это станет следующим кодом pesudo:

int foo() 
{ 
    A a; 
    A aa; 
    a_ctor(&a, 2); 
    a_ctor(&aa, 4); 

    A temp; 
    a_copy(temp, a_operator_plus(a, aa)); // temp is another "instance"  

    // no need to worry about the dtors, the return value references nothing from these objects that isn't in scope anymore. If it did then this would be an error even in C++, so don't worry about that 
    a_dtor(&aa); 
    a_dtor(&a); 

    return temp.mTest; 
} 

C++ «сгенерированный» код не будет вызывать dtors «после» оператора return. Dtors вызывается так же, как любая другая функция.

Предположим, C++ кода:

class A 
{ 
public: 
    A(const A&) = delete; 
    A& operator = (const A&) = delete; 
    A() 
    { 
    std::cout << "A ctor" << std::endl; 
    mExampleBuffer = new char[128]; // allocate resources example, we don't do anything with this.. 
    } 

    ~A() 
    { 
    std::cout << "A dtor" << std::endl; 
    delete[] mExampleBuffer; 
    } 

private: 
    char* mExampleBuffer = nullptr; // in real code this would be a std::vector or std::unique_ptr 
}; 

Затем используется как:

void foo() 
{ 
    A a; 
    return; // not required, but here for clarity 
} 

Тогда в C это будет:

struct A 
{ 
    // there is no "private" in C, so we need people to read this comment and not mess with mExampleBuffer 
    char* mExampleBuffer; 
}; 

void a_ctor(A* thisPtr) 
{ 
    printf("A ctor\n"); 
    thisPtr->mExampleBuffer = malloc(sizeof(char)*128); 
    if (!thisPtr->mExampleBuffer) 
    { 
     // TODO: In C++ this would throw, in C you're gonna have to use setlongjmp or some such to simulate it.. plus use some sort of "cleanupstack" to do the unwinding 
    } 
} 

void a_dtor(A* thisPtr) 
{ 
    printf("A dtor\n"); 
    free(thisPtr->mExampleBuffer); 
} 

void foo() 
{ 
    A a = {}; 
    a_ctor(&a); 
    a_dtor(&a); // nothing magic here, simply called before the return statement 
    return;  
} 

Как вы можете видеть, для многих классов используя «настоящий» C++ с RAII, это станет полным кошмаром. Также вы не принимаете во внимание, что фактическое сгенерированное код, вероятно, встраивать это так, что нет «класса», то есть это будет выглядеть примерно так:

void foo() 
{ 
    printf("A ctor\n"); 
    char* mExampleBuffer = malloc(sizeof(char)*128); // not sure if would remove this or not since not used :) didn't check 
    printf("A dtor\n"); 
    free(mExampleBuffer); 
    return;  
} 

Надеется, что это объясняет механизм dtor. Не забывайте, что с наследованием каждый dtor должен вызывать базу.

+0

В комментариях, которые вы сказали, и я цитирую: «сгенерированный код просто вызывает dtors перед возвратом, как любая нормальная функция», и теперь вы говорите, что «C++» сгенерированный «код не будет вызывать dtors» перед «оператором return», который эффективно что я пытался объяснить вам. Может быть, вы допустили процедуру 'ret' для' return statement'? Один возвращается к предыдущей точке выполнения, другой возвращает значение ... – 2014-09-28 10:58:44

+0

Я, вероятно, плохо сформулировал это, я имею в виду, что вы подразумеваете, что их вызывают после того, когда их нет, они называются как любая нормальная функция - редактируются:) – paulm

+0

Я не имел в виду, что они вызываются после 'ret', но после того, как возвращаемое значение было« возвращено »туда, где оно необходимо, которое еще находится до' ret'. – 2014-09-28 11:01:31

2

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

struct T { 
    char * data; 
    }; 

    void swap(T * a, T * b) { 
    swap(&a.data, &b.data); 
    } 

    void destruct(T & d) { 
    free(d.data); 
    } 

    void foo(T * rv) { 
     T x = {"Valueable data"}; 
     swap(rv, &x); //This is what return in C++ does 
     destruct(&x); //This happens, when function scope in C++ ends 
    } 

    void bar() { 
    T holder = {0}; 
    foo(holder); 
    destruct(&holder); 
    } 

Обратите внимание, что распределение и освобождение объекта всегда находятся в одном и том же объеме.

+0

Я действительно не обеспокоен перемещением «подвижных» данных, это просто хорошая практика и эффективность, а не копирование. Мой вопрос был строго о реальных местных жителях, а не их лежащих в основе. – 2014-09-28 11:41:14

+1

Моя точка зрения, функция C не должна позволять вызывающему коду делать очистку. Как только это условие будет выполнено, вы на один шаг ближе к RAII. – Basilevs

+0

@ user3735658 в этом случае это будет вызов вызова ctor вместо вызова свопинга – paulm

Смежные вопросы