C ++ подклассы модульных функций - PullRequest
0 голосов
/ 07 сентября 2011

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

Я хотел посмотреть, действительно ли нижеуказанное жизнеспособно:

Вот пример, который можно скомпилировать:

#include <stdio.h>

class TestA
{
    public:
        int A;

        TestA(){A = 1;}
        const TestA &operator=(const TestA &Copy)
        {
            A = Copy.A;
        }
};

class TestB : public TestA
{
    public:
        using TestA::operator=;
        void AdvancedFunction1(){A = A + A;}
};



int main()
{
    TestA Test;
    TestB *Alt;
    TestB Alt2;

    Alt = (TestB *)&Test;

    printf("%d!\n",Test.A);
    Alt->AdvancedFunction1();

    printf("%d!\n",Test.A);

    Test = Alt2;

    printf("%d!\n",Test.A);

    return 0;
}

Учитывая, что TestB может (с приведением) указывать на TestA и может успешно изменять TestA, это жизнеспособная концепция? Кажется, работает так, какие подводные камни? (Я знаю, что люди говорят, что это неправильно, но что это за конкретно неправильно с этим, кроме "конвенции"?).

Ответы [ 7 ]

1 голос
/ 07 сентября 2011
TestA Basic;
TestB *Advanced1 = &Basic; //Is this viable?
TestC *Advanced2 = &Basic; //And this?

Нет, ребенок не может указывать на объект типа отца.

TestB->AdvancedFunction1(); //Does this do what I think it does?
TestC->AdvancedFunction2(); //Or do implicit errors/problems occur?

Он вызывает TestB :: AdvancedFunction1 (то есть, если вы принудительно (приведение) и от Basic до Advanced1). Результаты могут быть катастрофическими.

TestB AdvVar1;
TestC AdvVar2;

AdvVar1 = Basic; //Does this do what is intended? 
AdvVar2 = Basic; //Or do implicit errors occur?

Совсем нет. Хотя иногда может быть разумным понизить (бросить отца к ребенку), копирование (если не определен оператор =) не работает.

Что вы должны сделать, если вы хотите, чтобы у вас был список детей с указателями на отца, вот что вы должны сделать:

class TestA
{
public:
    enum _type
    {
         IS_TESTA = 0,
         IS_TESTB,
         IS_TESTC
    } type;
    /* other variables */
    TestA() {type = IS_TESTA;}
    virtual void common_function() { /* do something with TestA data */ }
    void father_function() {}
}

class TestB : public TestA
{
public:
    /* variables */
    TestB() {type = TestA::IS_TESTB;}
    void common_function() { /* do something with TestA & TestB data */ }
    void TestB_specific_function() {}
}

class TestC : public TestA
{
public:
    /* variables */
    TestC() {type = TestA::IS_TESTC;}
    void common_function() { /* do something with TestA & TestC data */ }
    void TestC_specific_function() {}
}

Чтобы создать список, вы делаете это:

TestA **list = new TestA *[size];
for (int i = 0; i < size; ++i)
    if (for_any_reason_create_TestB)
        list[i] = new TestB;
    else if (for_any_reason_create_TestC)
        list[i] = new TestC;
    else
        list[i] = new TestA;

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

for (int i = 0; i < size; ++i)
    list[i]->common_function();

И он будет автоматически вызывать TestA::common_function, TestB::common_function или TestC::common_function в зависимости от типа реального объекта.

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

for (int i = 0; i < size; ++i)
    if (list[i]->type == TestA::IS_TESTB)
        ((TestB *)list[i])->TestB_specific_function();
    else if (list[i]->type == TestA::IS_TESTC)
        ((TestC *)list[i])->TestC_specific_function();
1 голос
/ 07 сентября 2011

vi · a · ble

Прилагательное / ˈvīəbəl /

  1. Способность к успешной работе;осуществимо

Если это работает, это жизнеспособно.

В вашем случае это работает.Однако рассмотрим следующие 2 сценария:

- Вы можете добавить виртуальные функции.Это приведет к его падению.

-Вы можете изменить AdvancedFunction1(), чтобы он работал с членами класса TestB, что приведет к неопределенному поведению, поскольку ваш объект не относится к типу TestB и не имеет членов TestB.

РЕДАКТИРОВАТЬ:

Код аварии:

class TestA
{
    public:
        int A;

        TestA(){A = 1;}
        const TestA &operator=(const TestA &Copy)
        {
            A = Copy.A;
        return *this;
        }
};

class TestB : public TestA
{
    public:
        using TestA::operator=;
        void AdvancedFunction1(){A = A + A;}
    virtual void f()
    {
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    TestA Test;
    TestB *Alt;
    Alt = (TestB *)&Test;
    Alt->AdvancedFunction1();
    Alt->f(); //crash here

    return 0;
}
1 голос
/ 07 сентября 2011

Приведенный выше код вызывает демона Неопределенное поведение .Это формально недопустимый код (при условии, что вы добавите static_cast, без которого он не будет компилироваться), и компилятор будет рад придумать, как превратить вашу кошку в черный цвет.

Gcc особенно известен тем, что онтворчество в наказании программистов, которые осмеливаются вызывать зверя.В вашем случае было бы весьма вероятно либо постоянно распространять одни и те же значения в обе функции, потому что спецификация говорит, что Advanced1 и Advanced2 не могут указывать на один и тот же объект, или оптимизировать вызовы, потому что спецификация говорит, что ни один из них не может указывать на тот же объект, что иОсновной несмотря на все признаки того, что они на самом деле.И поверьте мне, если что-то подобное происходит в каком-то креативном коде, то очень трудно отлаживать.

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

void AdvancedFunction1(TestA &that) { that.A = that.A + that.A; }

, а затем:

TestA test;
AdvancedFunction1(test);

С другой стороны: полностью и очень глупо полностью переделывать вопрос, когдау вас уже есть ответы на некоторые вопросы.Первая версия вопроса имела Advanced1 и Advanced2 и Basic, а теперь у нее совершенно разные имена.

1 голос
/ 07 сентября 2011

номер

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

Теперь предположим, что вы имели в виду:

Advanced1->AdvancedFunction1(); 
Advanced2->AdvancedFunction2();

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

Но TestA не является TestB, поэтому вы не можете назначить TestA для TestB.

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

1 голос
/ 07 сентября 2011

Я бы этого не делал - правильно используйте наследование, т.е.

TestA* Advanced1 = new TestB;
TestA* Advanced2 = new TestC;

// your previous code at this point is wrong - these functions should be virtual in base class and overridden in derived classes...
Advanced1->AdvancedFunction1(); // This now does what you want
Advanced2->AdvancedFunction2(); // and so does this...

Назначение в порядке ...

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

1 голос
/ 07 сентября 2011

TestB * Advanced1 = & Basic;// Это жизнеспособно?

Нет, потому что апкастинг не является хорошей идеей.

Вы должны использовать полиморфизм, создавая виртуальную функцию в своей базе, а затем переопределяя функцию вПроизводное.

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

1 голос
/ 07 сентября 2011

То, что вы делаете, называется upcasting . Upcasting - это когда вы берете указатель на производный тип и пытаетесь присвоить его базовому типу. Это не работает.

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

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

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

...