18.1 Pointers and references to the base class of derived objects

주홍영·2022년 3월 21일
0

Learncpp.com

목록 보기
172/199

https://www.learncpp.com/cpp-tutorial/pointers-and-references-to-the-base-class-of-derived-objects/

저번 챕터에서 우리는 inheritance를 통해 존재하는 클래스를 상속받아 새로운 클래스를 생성하는 것에 대해서 배웠다. 이번 챕터에서는 virtual function에 대해서 배운다

우리는 virtual function이 뭔지 논의하기 전에 왜 필요한지에 대해서 이야기 해보자

#include <string_view>

class Base
{
protected:
    int m_value {};

public:
    Base(int value)
        : m_value{ value }
    {
    }

    std::string_view getName() const { return "Base"; }
    int getValue() const { return m_value; }
};

class Derived: public Base
{
public:
    Derived(int value)
        : Base{ value }
    {
    }

    std::string_view getName() const { return "Derived"; }
    int getValueDoubled() const { return m_value * 2; }
};

먼저 위와 같은 상속관계의 두 클래스가 있다고 하자

Pointers, references, and derived classes

우리는 클래스 또한 pointer와 reference를 이용해 생성할 수 있다

#include <iostream>

int main()
{
    Derived derived{ 5 };
    std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n';

    Derived& rDerived{ derived };
    std::cout << "rDerived is a " << rDerived.getName() << " and has value " << rDerived.getValue() << '\n';

    Derived* pDerived{ &derived };
    std::cout << "pDerived is a " << pDerived->getName() << " and has value " << pDerived->getValue() << '\n';

    return 0;
}

위와 같은 코드의 출력은 다음과 같다

derived is a Derived and has value 5
rDerived is a Derived and has value 5
pDerived is a Derived and has value 5

그런데 Derived는 Base part를 지니고 있다. 흥미로운 정믄 c++은 Derived object를
Base pointer 혹은 reference로 받는 것을 허용하고 있다
따라서 우리는 다음과 같이 할 수 있다

#include <iostream>

int main()
{
    Derived derived{ 5 };

    // These are both legal!
    Base& rBase{ derived };
    Base* pBase{ &derived };

    std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n';
    std::cout << "rBase is a " << rBase.getName() << " and has value " << rBase.getValue() << '\n';
    std::cout << "pBase is a " << pBase->getName() << " and has value " << pBase->getValue() << '\n';

    return 0;
}

위의 코드의 출력은 다음과 같다

derived is a Derived and has value 5
rBase is a Base and has value 5
pBase is a Base and has value 5

Derived object를 Base pointer와 reference로 받으니
member function을 사용하면 Base로 인식하고 사용되고 있다

여기 조금더 복잡한 example을 살펴보자

#include <iostream>
#include <string_view>
#include <string>

class Animal
{
protected:
    std::string m_name;

    // We're making this constructor protected because
    // we don't want people creating Animal objects directly,
    // but we still want derived classes to be able to use it.
    Animal(std::string_view name)
        : m_name{ name }
    {
    }

    // To prevent slicing (covered later)
    Animal(const Animal&) = default;
    Animal& operator=(const Animal&) = default;

public:
    std::string_view getName() const { return m_name; }
    std::string_view speak() const { return "???"; }
};

class Cat: public Animal
{
public:
    Cat(std::string_view name)
        : Animal{ name }
    {
    }

    std::string_view speak() const { return "Meow"; }
};

class Dog: public Animal
{
public:
    Dog(std::string_view name)
        : Animal{ name }
    {
    }

    std::string_view speak() const { return "Woof"; }
};

int main()
{
    const Cat cat{ "Fred" };
    std::cout << "cat is named " << cat.getName() << ", and it says " << cat.speak() << '\n';

    const Dog dog{ "Garbo" };
    std::cout << "dog is named " << dog.getName() << ", and it says " << dog.speak() << '\n';

    const Animal* pAnimal{ &cat };
    std::cout << "pAnimal is named " << pAnimal->getName() << ", and it says " << pAnimal->speak() << '\n';

    pAnimal = &dog;
    std::cout << "pAnimal is named " << pAnimal->getName() << ", and it says " << pAnimal->speak() << '\n';

    return 0;
}

위 코드의 출력은 다음과 같다

cat is named Fred, and it says Meow
dog is named Garbo, and it says Woof
pAnimal is named Fred, and it says ???
pAnimal is named Garbo, and it says ???

pAnimal에서 name의 경우 Base class인 Animal에 저장되므로 원래 이름이 출력된다
하지만 speak()는 Animal pointer이기 때문에 ???가 출력됨을 알 수 있다

Use for pointers and references to base classes

이렇게 보면 parent class pointer 혹은 reference로 받는 이유가 무엇인지 궁금하다
사례로만 봤을 때 딱히 효용이 보이지 않기 때문이다
여기 몇몇 이유가 있다

첫째, 우리가 만약 Dog와 Cat의 이름과 울음소리를 출력하는 함수를 만들고 싶다 (normal function으로)
그러면 다음과 같이 코드를 작성할 것이다

void report(const Cat& cat)
{
    std::cout << cat.getName() << " says " << cat.speak() << '\n';
}

void report(const Dog& dog)
{
    std::cout << dog.getName() << " says " << dog.speak() << '\n';
}

지금은 두마리 뿐이라 금방 쓸 수 있겠지만 30마리가 animal에서 파생되었다고 가정하면 똑같은 형태의 함수를 변수만 조금씩 바꿔서 30번 반복해야한다. 이는 굉장히 비효율적으로 보인다

하지만 Cat과 Dog 모두 animal에서 파생되었으므로 다음과 같이 함수를 만들 수도 있다

void report(const Animal& rAnimal)
{
    std::cout << rAnimal.getName() << " says " << rAnimal.speak() << '\n';
}

다만 member function이 모두 Animal의 member function이기 때문에 현재로는 의도한 동작이 되지는 않는다

둘째, Cat과 Dog로 array를 만든다고 하자

#include <array>
#include <iostream>

// Cat and Dog from the example above

int main()
{
    const auto& cats{ std::to_array<Cat>({{ "Fred" }, { "Misty" }, { "Zeke" }}) };
    const auto& dogs{ std::to_array<Dog>({{ "Garbo" }, { "Pooky" }, { "Truffle" }}) };

    // Before C++20
    // const std::array<Cat, 3> cats{{ { "Fred" }, { "Misty" }, { "Zeke" } }};
    // const std::array<Dog, 3> dogs{{ { "Garbo" }, { "Pooky" }, { "Truffle" } }};

    for (const auto& cat : cats)
    {
        std::cout << cat.getName() << " says " << cat.speak() << '\n';
    }

    for (const auto& dog : dogs)
    {
        std::cout << dog.getName() << " says " << dog.speak() << '\n';
    }

    return 0;
}

위와 같이 쓸 수 있다. 그런데 만약 30종의 동물이라면 30개의 다른 array를 만들어야 한다

하지만 모두 Animal에서 파생되었으므로 다음과 같이 작성할수 있다

#include <array>
#include <iostream>

// Cat and Dog from the example above

int main()
{
    const Cat fred{ "Fred" };
    const Cat misty{ "Misty" };
    const Cat zeke{ "Zeke" };

    const Dog garbo{ "Garbo" };
    const Dog pooky{ "Pooky" };
    const Dog truffle{ "Truffle" };

    // Set up an array of pointers to animals, and set those pointers to our Cat and Dog objects
    // Note: to_array requires C++20 support (and at the time of writing, Visual Studio 2022 still doesn't support it correctly)
    const auto animals{ std::to_array<const Animal*>({&fred, &garbo, &misty, &pooky, &truffle, &zeke }) };

    // Before C++20, with the array size being explicitly specified
    // const std::array<const Animal*, 6> animals{ &fred, &garbo, &misty, &pooky, &truffle, &zeke };

    for (const auto animal : animals)
    {
        std::cout << animal->getName() << " says " << animal->speak() << '\n';
    }

    return 0;
}

위와 같이 Animal type의 array에 두 종류의 object를 모두 담아둘 수 있다

하지만 마찬가지로 Animal member function이 실행된다는 문제점이 존재한다

이러한 이유로 virtual function이 필요하게 된 것이다

profile
청룡동거주민

0개의 댓글