overloading을 하면 overriding이 되어 vector의 오리지날 함수를 호출할 수 없게 됩니다.
namespace의 차이가 원인은 아닌 것은 확인했습니다.
원인을 아시는 분 알려주시면 고맙겠습니다. :-)
상속받은 클래스의 정의를 봐야 확실히 알 수 있겠습니다. 그런데 이런 동작을
보이는 것은 보통 기초 클래스의 멤버를 숨겼을 때 나타나는 현상입니다.
class Base
{
public:
void f(char*);
};
class Derived : public Base
{
public:
void f(int); // hide Base::f(char*)
};
Derived d;
d.f("C++"); // error
여기서 Derived::f는 Base::f와 인자가 다르므로 오버라이딩한 것이 아니라
숨긴 것입니다. 따라서 Derived 개체는 Base::f(char*)를 쓸 수 없게 됩니다.
오버라이딩할 때는 인자를 같게 하든지 using-declaration을 써야 합니다.
class Derived : public Base
{
public:
using Base::f; // get access to Base::f(char*)
void f(char*); // override Base::f(char*)
void f(int); // overload Derived::f(char*)
};
vector의 멤버 함수를 재정의하고자 하면 오버라이딩을, 그대로 쓰고 싶으시다면
using-declaration을 쓰면 됩니다.
네, 소멸자가 가상이 아니라는 것은 다형적인 클래스 계층의 기초 클래스로
사용되지 말아야 한다는 의도를 나타내는 것이 맞긴 합니다. 그러나 그런 이유로
vector를 상속받지 말아야 한다는 건 좀 억울하군요. :) 예를 들어
template<typename T>
class Vec : public vector<T>
{
/* ... */
};
vector<int>* p = new Vec<int>;
delete p; // oops
이때 문제가 되는 경우는 vector*로 Vec를 동적 할당할 때뿐입니다. 이 점만
주의하면 별로 문제될 것은 없다고 봅니다. TC++PL에도 이런 예가 나와 있고
저도 그렇게 쓴 경험이 있거든요. 사실 컨테이너 클래스를 동적으로 생성할
이유는 별로 없지 않을까요?
네 맞는 말씀입니다만, 꼭 그렇게 사용하지 말라는 법은 없으니까요. Vec 를 new 못하게 금지할 방법을 아예 제공하든지 해야겠죠. 문제가 될 소지가 있다는 것을 말씀드린겁니다.
그리고, 컨테이너도 new 되는 경우가 좀 있습니다. 예를 들자면 헤더의 종속성을 회피하기 위해서 또는 헤더의 부담을 덜기위해서 클래스 선언에 컨테이너 본체를 넣지않고 컨테이너의 포인터 타입을 넣는 경우가 있습니다. 그런 경우라면 컨테이너를 상속한 것을 쓰기에 좀 위험한 경우가 될 수 있겠습니다. (물론 auto_ptr 을 쓰거나 직접 상속된 타입의 멤버를 delete 하겠지만서도...)
혼자서 사용하는 라이브러리라면 뭐라고 드릴 말씀이 없지만, 다른 사람에게 제공해야 하는 것들이라면 조금 생각해봐야 할 문제가 아닌가 합니다.
네 맞는 말씀입니다만, 꼭 그렇게 사용하지 말라는 법은 없으니까요. Vec 를 new 못하게 금지할 방법을 아예 제공하든지 해야겠죠. 문제가 될 소지가 있다는 것을 말씀드린겁니다.
그렇군요. new를 막으려면 다음과 같이 하면 되겠네요.
template<typename T>
class Vec : public vector<T>
{
private:
static void* operator new(size_t);
static void operator delete(void*);
};
bugiii wrote:
그리고, 컨테이너도 new 되는 경우가 좀 있습니다. 예를 들자면 헤더의 종속성을 회피하기 위해서 또는 헤더의 부담을 덜기위해서 클래스 선언에 컨테이너 본체를 넣지않고 컨테이너의 포인터 타입을 넣는 경우가 있습니다. 그런 경우라면 컨테이너를 상속한 것을 쓰기에 좀 위험한 경우가 될 수 있겠습니다. (물론 auto_ptr 을 쓰거나 직접 상속된 타입의 멤버를 delete 하겠지만서도...)
pimpl idiom을 말씀하시는가 봅니다. 이 부분은 어떤 때 문제가 생기는지
구체적으로 예를 들어주시면 고맙겠습니다. 저는 잘 생각이 안나서요.
네 맞는 말씀입니다만, 꼭 그렇게 사용하지 말라는 법은 없으니까요. Vec 를 new 못하게 금지할 방법을 아예 제공하든지 해야겠죠. 문제가 될 소지가 있다는 것을 말씀드린겁니다.
그렇군요. new를 막으려면 다음과 같이 하면 되겠네요.
template<typename T>
class Vec : public vector<T>
{
private:
static void* operator new(size_t);
static void operator delete(void*);
};
bugiii wrote:
그리고, 컨테이너도 new 되는 경우가 좀 있습니다. 예를 들자면 헤더의 종속성을 회피하기 위해서 또는 헤더의 부담을 덜기위해서 클래스 선언에 컨테이너 본체를 넣지않고 컨테이너의 포인터 타입을 넣는 경우가 있습니다. 그런 경우라면 컨테이너를 상속한 것을 쓰기에 좀 위험한 경우가 될 수 있겠습니다. (물론 auto_ptr 을 쓰거나 직접 상속된 타입의 멤버를 delete 하겠지만서도...)
pimpl idiom을 말씀하시는?봅니다. 이 부분은 어떤 때 문제가 생기는지
구체적으로 예를 들어주시면 고맙겠습니다. 저는 잘 생각이 안나서요.
new 는 형태가 배열도 있고 하니, 막아야 한다면, 여러가지를 다 막아야 하겠습니다.
구현과 인터페이스를 구별하는 작업에서도 처음 말씀드린 문제가 생기지 않을까요? (new 를 컨테이너 자체에 사용하는 경우도 있다는게 주된 요지였습니다...)
어거지로(!) 예를 한번 든다면, B -> D 이렇게 상속받고, B 의 멤버로 std::vector 의 포인터를 선언하고 D 에서 new Vec 로 초기화 해버리면.... 마찬가지 결과가 아닐까 합니다.
B( vector<...>* v ) { ... }
~B() { delete v_; } // <--- !!! 혹은 생성자에서 auto_ptr 을 이용하든 oops
D( ... ) : B( new Vec ) { ... }
진짜 가상 소멸자가 없이도 public 상속 (private 상속은 상관없죠) 이 문제없이 동작되는 경우는 몇 안되는 걸로 알고 있습니다. 그중 제가 알고 있는 하나는 C 구조체를 편하게 쓰기위해서 초기화하는 생성자를 덛붙히는 정도입니다.
이렇게 하면 아무런 문제가 없겠죠. 물론 B를 상속해도 됩니다. 이런 클래스가
별 의미없다고 한 이유는 아무리 봐도 컨테이너 어댑터 외에는 쓸모가 없는 것
같아서입니다. 그리고 컨테이너 어댑터로 쓸 생각이면 차라리 포인터가 아니라
컨테이너 자체를 갖고 있는 것이 자연스러워 보이기 때문입니다. std::stack이나
std::queue처럼 말이죠.
이와는 별도로 export가 지원되지 않는 이상 인터페이스와 구현의 분리라는 문제가
남기는 하네요.
2. deallocate는 vector에서 담당하는 것이니 buffer의 소멸자가 걱정할 일이
아닙니다. 지금은 소멸자를 따로 정의할 필요가 없습니다.
요건 bugiii님의 말씀처럼 vector의 소멸자가 virtual이 아닌 관계로 필요한 부분이 아닌지요?
그렇게 한다고 해서 문제가 해결되는 것은 아닙니다.
vector<int>* p1 = new buffer<int>;
delete p1;
buffer<int>* p2 = new buffer<int>;
delete p2;
이렇게 했다고 하죠. ~vector()는 가상이 아니므로 p1을 delete 할 때는
~buffer()가 아니라 ~vector()가 호출된다는 것이 문제입니다.
그리고 p2를 delete 할 때는 deallocate가 두 번 실행되겠죠. (~buffer()와
~vector()에서 각각 한 번씩) 역시 잘못입니다. 남이 할 일을 대신 떠맡지 맙시다. :)
doldori 님처럼 알아서 잘 처리하시는 분들이라면 라이브러리 만드는 분들이 걱정없이 세상을 살아갈 수 있을 것입니다만... 세상에는 별의별 사용자들이 많다보니, 주의사항을 명시하거나 (초보자는 잘 안읽을 것이고 잘 하시는 분들은 알아서 피하실거니 또 안읽을 것이지만) 사용상의 제약을 가하는 것이겠죠.
C++ 클래스라는 것이 너무나도 자유도가 있어서 별의별 상황이 다 연출되니까, 심지어는 상속이라는 것 자체가 두려워질 때도 있습니다. 뭐 요즘 유행이 제네릭 스타일이라서 더욱 그런지도 모르겠습니다만...
하여간 doldori 님은 가상 소멸자가 없을 때의 문제점을 피할 수 있는 방법을 알고 계시지만, 초심자들에게는 상당히 심각한 (또한 사실을 인지하지 못하고) 고민일 수도 있습니다.
저는 아예 가상 소멸자가 없는 클래스는 특수한 경우를 제외하고는 상속하지 말라고 권하고 싶습니다만, doldori 님은 피해가는 방법을 알려주고 싶어하시는 것 같습니다.
lnsium 님에게 이와 관련된 내용은 Meyers 의 유명한 책들을 한번 참고해보시기를 권해드립니다. lnsium 의 상황에 맞는 챕터가 있었던 걸로 기억합니다. 제 기억이 맞다면 아마도 More Effective C++ 의 후반부정도라고 생각되는데요. 저와 doldori 님의 입장차이를 설명해놓았습니다. 보시면 분명히 도움이 되리라 생각합니다.
이것 저것 다 제쳐두고, std::vector 는 상속하지 마세요.
이것 저것 다 제쳐두고, std::vector 는 상속하지 마세요.
소멸자가... virtual 이 아닙니다.
stl 중에 가상 소멸자를 가지고 있는 클래스나 템플릿은 별로 없습니다...
Re: STL vector 상속시 문제점에 대해...
상속받은 클래스의 정의를 봐야 확실히 알 수 있겠습니다. 그런데 이런 동작을
보이는 것은 보통 기초 클래스의 멤버를 숨겼을 때 나타나는 현상입니다.
여기서 Derived::f는 Base::f와 인자가 다르므로 오버라이딩한 것이 아니라
숨긴 것입니다. 따라서 Derived 개체는 Base::f(char*)를 쓸 수 없게 됩니다.
오버라이딩할 때는 인자를 같게 하든지 using-declaration을 써야 합니다.
vector의 멤버 함수를 재정의하고자 하면 오버라이딩을, 그대로 쓰고 싶으시다면
using-declaration을 쓰면 됩니다.
[quote="bugiii"]이것 저것 다 제쳐두고, std::vecto
네, 소멸자가 가상이 아니라는 것은 다형적인 클래스 계층의 기초 클래스로
사용되지 말아야 한다는 의도를 나타내는 것이 맞긴 합니다. 그러나 그런 이유로
vector를 상속받지 말아야 한다는 건 좀 억울하군요. :) 예를 들어
이때 문제가 되는 경우는 vector*로 Vec를 동적 할당할 때뿐입니다. 이 점만
주의하면 별로 문제될 것은 없다고 봅니다. TC++PL에도 이런 예가 나와 있고
저도 그렇게 쓴 경험이 있거든요. 사실 컨테이너 클래스를 동적으로 생성할
이유는 별로 없지 않을까요?
[quote="doldori"][quote="bugiii"]이것 저것 다
네 맞는 말씀입니다만, 꼭 그렇게 사용하지 말라는 법은 없으니까요. Vec 를 new 못하게 금지할 방법을 아예 제공하든지 해야겠죠. 문제가 될 소지가 있다는 것을 말씀드린겁니다.
그리고, 컨테이너도 new 되는 경우가 좀 있습니다. 예를 들자면 헤더의 종속성을 회피하기 위해서 또는 헤더의 부담을 덜기위해서 클래스 선언에 컨테이너 본체를 넣지않고 컨테이너의 포인터 타입을 넣는 경우가 있습니다. 그런 경우라면 컨테이너를 상속한 것을 쓰기에 좀 위험한 경우가 될 수 있겠습니다. (물론 auto_ptr 을 쓰거나 직접 상속된 타입의 멤버를 delete 하겠지만서도...)
혼자서 사용하는 라이브러리라면 뭐라고 드릴 말씀이 없지만, 다른 사람에게 제공해야 하는 것들이라면 조금 생각해봐야 할 문제가 아닌가 합니다.
doldori님 감사합니다..:-)
doldori님의 가르침을 따른 결과 너무도 잘 해결됐습니다.
doldori님께 감사드립니다. 다른 분들도 감사합니다..꾸벅 :-)
소스를 올려봅니다.
도움이 되었다니 기쁩니다. 그런데 buffer 클래스에 몇 가지 문제점이
도움이 되었다니 기쁩니다. 그런데 buffer 클래스에 몇 가지 문제점이 보이네요.
1. typedef 한 것들은
typedef typename vector<_Ty, _A>::allocator_type allocator_type;
같은 식으로 쓰는 것이 좋습니다. vector에서 상속받았으므로 vector에서
정의한 타입을 그대로 받아쓰세요.
2. deallocate는 vector에서 담당하는 것이니 buffer의 소멸자가 걱정할 일이
아닙니다. 지금은 소멸자를 따로 정의할 필요가 없습니다.
3. 복사생성자, 대입연산자는 따로 정의할 필요 없습니다. 컴파일러가 자동으로
만들어주는 코드가 정의하신 것과 똑같으니까요.
4. get() 같은 함수에서 iterator와 pointer를 섞어쓰고 있군요. 이 둘은 완전히
다른 타입이라고 생각하세요. STL의 관례를 따른다면 iterator를 써야겠죠.
[quote="bugiii"]네 맞는 말씀입니다만, 꼭 그렇게 사용하
그렇군요. new를 막으려면 다음과 같이 하면 되겠네요.
pimpl idiom을 말씀하시는가 봅니다. 이 부분은 어떤 때 문제가 생기는지
구체적으로 예를 들어주시면 고맙겠습니다. 저는 잘 생각이 안나서요.
[quote="doldori"][quote="bugiii"]네 맞는
new 는 형태가 배열도 있고 하니, 막아야 한다면, 여러가지를 다 막아야 하겠습니다.
구현과 인터페이스를 구별하는 작업에서도 처음 말씀드린 문제가 생기지 않을까요? (new 를 컨테이너 자체에 사용하는 경우도 있다는게 주된 요지였습니다...)
어거지로(!) 예를 한번 든다면, B -> D 이렇게 상속받고, B 의 멤버로 std::vector 의 포인터를 선언하고 D 에서 new Vec 로 초기화 해버리면.... 마찬가지 결과가 아닐까 합니다.
B( vector<...>* v ) { ... }
~B() { delete v_; } // <--- !!! 혹은 생성자에서 auto_ptr 을 이용하든 oops
D( ... ) : B( new Vec ) { ... }
진짜 가상 소멸자가 없이도 public 상속 (private 상속은 상관없죠) 이 문제없이 동작되는 경우는 몇 안되는 걸로 알고 있습니다. 그중 제가 알고 있는 하나는 C 구조체를 편하게 쓰기위해서 초기화하는 생성자를 덛붙히는 정도입니다.
단상...
수정했습니다..^^;;
요건 bugiii님의 말씀처럼 vector의 소멸자가 virtual이 아닌 관계로 필요한 부분이 아닌지요?
get이 원래 non-stl처리를 위해 있는 거라서리...-.-;;
관심 감사합니다..:-)
[quote="bugiii"]구현과 인터페이스를 구별하는 작업에서도
저도 bugiii님이 보여주신 코드와 비슷한 경우를 떠올려보긴 했는데요. 아무리
생각해도 이런 클래스는 별 의미가 없는 것 같거든요. 저도 '어거지' :) 코드를
생각해 봤는데
이렇게 하면 아무런 문제가 없겠죠. 물론 B를 상속해도 됩니다. 이런 클래스가
별 의미없다고 한 이유는 아무리 봐도 컨테이너 어댑터 외에는 쓸모가 없는 것
같아서입니다. 그리고 컨테이너 어댑터로 쓸 생각이면 차라리 포인터가 아니라
컨테이너 자체를 갖고 있는 것이 자연스러워 보이기 때문입니다. std::stack이나
std::queue처럼 말이죠.
이와는 별도로 export가 지원되지 않는 이상 인터페이스와 구현의 분리라는 문제가
남기는 하네요.
Re: 단상...
그렇게 한다고 해서 문제가 해결되는 것은 아닙니다.
이렇게 했다고 하죠. ~vector()는 가상이 아니므로 p1을 delete 할 때는
~buffer()가 아니라 ~vector()가 호출된다는 것이 문제입니다.
그리고 p2를 delete 할 때는 deallocate가 두 번 실행되겠죠. (~buffer()와
~vector()에서 각각 한 번씩) 역시 잘못입니다. 남이 할 일을 대신 떠맡지 맙시다. :)
doldori 님처럼 알아서 잘 처리하시는 분들이라면 라이브러리 만드는
doldori 님처럼 알아서 잘 처리하시는 분들이라면 라이브러리 만드는 분들이 걱정없이 세상을 살아갈 수 있을 것입니다만... 세상에는 별의별 사용자들이 많다보니, 주의사항을 명시하거나 (초보자는 잘 안읽을 것이고 잘 하시는 분들은 알아서 피하실거니 또 안읽을 것이지만) 사용상의 제약을 가하는 것이겠죠.
C++ 클래스라는 것이 너무나도 자유도가 있어서 별의별 상황이 다 연출되니까, 심지어는 상속이라는 것 자체가 두려워질 때도 있습니다. 뭐 요즘 유행이 제네릭 스타일이라서 더욱 그런지도 모르겠습니다만...
하여간 doldori 님은 가상 소멸자가 없을 때의 문제점을 피할 수 있는 방법을 알고 계시지만, 초심자들에게는 상당히 심각한 (또한 사실을 인지하지 못하고) 고민일 수도 있습니다.
저는 아예 가상 소멸자가 없는 클래스는 특수한 경우를 제외하고는 상속하지 말라고 권하고 싶습니다만, doldori 님은 피해가는 방법을 알려주고 싶어하시는 것 같습니다.
lnsium 님에게 이와 관련된 내용은 Meyers 의 유명한 책들을 한번 참고해보시기를 권해드립니다. lnsium 의 상황에 맞는 챕터가 있었던 걸로 기억합니다. 제 기억이 맞다면 아마도 More Effective C++ 의 후반부정도라고 생각되는데요. 저와 doldori 님의 입장차이를 설명해놓았습니다. 보시면 분명히 도움이 되리라 생각합니다.
좋은 의견 감사드립니다.
PC 방이라는 걸 깜빡했군요..... 로긴 로긴....
PC 방이라는 걸 깜빡했군요..... 로긴 로긴....
저도 여러 가지를 생각해 볼 수 있는 계기가 되었습니다. 이번 토론으로
저도 여러 가지를 생각해 볼 수 있는 계기가 되었습니다. 이번 토론으로 많은 것을
얻었군요. 즐거웠습니다. :)
댓글 달기