포인터를 무효화하기 위해 동적 캐스팅에 대한 실용적인 용도가 있습니까?
C ++에서 T q = dynamic_cast<T>(p);
생성은 성공하기 위해 동적 유형의 상속 계층에 나타나야하는 p
다른 포인터 유형 에 대한 포인터의 런타임 캐스트를 수행 T
합니다 *p
. 그것은 모두 훌륭하고 좋습니다.
그러나 dynamic_cast<void*>(p)
"가장 많이 파생 된 객체"에 대한 포인터를 반환하는를 수행 할 수도 있습니다 (C ++ 11의 5.2.7 :: 7 참조). 이 기능은 동적 캐스트 구현시 무료로 제공 될 수 있지만 실제로 유용합니까? 결국 반환 유형은 기껏 void*
해야하는데이게 무슨 소용일까요?
은 dynamic_cast<void*>()
참으로도 다중 상속을 다루는 경우 신원을 확인하는 데 사용할 수 있습니다.
이 코드를 시도하십시오.
#include <iostream>
class B {
public:
virtual ~B() {}
};
class D1 : public B {
};
class D2 : public B {
};
class DD : public D1, public D2 {
};
namespace {
bool eq(B* b1, B* b2) {
return b1 == b2;
}
bool eqdc(B* b1, B *b2) {
return dynamic_cast<void*>(b1) == dynamic_cast<void*>(b2);
}
};
int
main() {
DD *dd = new DD();
D1 *d1 = dynamic_cast<D1*>(dd);
D2 *d2 = dynamic_cast<D2*>(dd);
std::cout << "eq: " << eq(d1, d2) << ", eqdc: " << eqdc(d1, d2) << "\n";
return 0;
}
산출:
eq: 0, eqdc: 1
C ++를 사용하면 이전 C 방식으로 작업을 수행 할 수 있습니다.
유형을 통해 객체 포인터를 밀수해야하는 API가 void*
있지만 결국 전달되는 콜백이 동적 유형을 알고 있다고 가정합니다.
struct BaseClass {
typedef void(*callback_type)(void*);
virtual callback_type get_callback(void) = 0;
virtual ~BaseClass() {}
};
struct ActualType: BaseClass {
callback_type get_callback(void) { return my_callback; }
static void my_callback(void *p) {
ActualType *self = static_cast<ActualType*>(p);
...
}
};
void register_callback(BaseClass *p) {
// service.register_listener(p->get_callback(), p); // WRONG!
service.register_listener(p->get_callback(), dynamic_cast<void*>(p));
}
잘못되었습니다! 코드는 다중 상속이있는 경우 실패하기 때문에 잘못되었습니다 (그리고 부재시에도 작동이 보장되지 않음).
물론 API는 C ++ 스타일이 아니며 .NET에서 상속하면 "올바른"코드도 잘못 될 수 있습니다 ActualType
. 그래서 나는 이것이의 훌륭한 사용이라고 주장 dynamic_cast<void*>
하지는 않지만 사용입니다.
포인터를 캐스트 void*
하는 것은 C 일 이후로 그 중요성이 있습니다. 가장 적합한 위치는 운영 체제의 메모리 관리자 내부입니다. 그것은 당신이 만든 모든 포인터와 개체를 저장해야합니다. * 공간에 저장하여 그들이 될 수있는 메모리 관리 데이터 구조에 물체를 저장하기 위해 그것을 일반화 heap/B+Tree
또는 단순 arraylist
.
단순화를 위해 list
제네릭 항목 (목록에는 완전히 다른 클래스의 항목이 포함됨) 을 생성하는 예제가 있습니다. 그것은 void*
.
standard는 dynamic_cast가 잘못된 유형 캐스팅에 대해 null을 반환해야한다고 말하며 표준은 또한 모든 포인터가 함수 포인터 만 제외하고 void *로 유형 캐스팅하고 다시 돌아올 수 있도록 보장합니다.
일반적인 응용 수준의 실제 사용은 void*
형변환에 매우 적지 만 저수준 / 임베디드 시스템에서 광범위하게 사용됩니다.
일반적으로 8086에서 주소를 얻기 위해 동일한 기준의 포인터를 오프셋하는 데 사용되지만 이에 제한되지 않는 것처럼 저수준 항목에 reinterpret_cast를 사용하고 싶을 것입니다.
편집 : Standard는 포인터를 void*
짝수 로 변환 할 수 있다고 말하지만 다시 개체 dynamic_cast<>
로 변환 할 수 없다는 상태는 없습니다 void*
.
대부분의 사용에는 일방 통행이지만 피할 수없는 사용이 있습니다.
dynamic_cast<>
요청 된 유형으로 다시 변환하기 위해 유형 정보 가 필요 하다고 말합니다 .
void*
예를 들어 일부 개체 에 전달해야하는 많은 API가 있습니다 . java / Jni 코드는 객체를 void*
.
유형 정보가 없으면 캐스팅을 할 수 없습니다. 요청한 유형이 정확하다고 확신하는 경우 컴파일러에게 dynmaic_cast<>
트릭 을 사용하도록 요청할 수 있습니다 .
이 코드를보십시오 :
class Base_Class {public : virtual void dummy() { cout<<"Base\n";} };
class Derived_Class: public Base_Class { int a; public: void dummy() { cout<<"Derived\n";} };
class MostDerivedObject : public Derived_Class {int b; public: void dummy() { cout<<"Most\n";} };
class AnotherMostDerivedObject : public Derived_Class {int c; public: void dummy() { cout<<"AnotherMost\n";} };
int main () {
try {
Base_Class * ptr_a = new Derived_Class;
Base_Class * ptr_b = new MostDerivedObject;
Derived_Class * ptr_c,*ptr_d;
ptr_c = dynamic_cast< Derived_Class *>(ptr_a);
ptr_d = dynamic_cast< Derived_Class *>(ptr_b);
void* testDerived = dynamic_cast<void*>(ptr_c);
void* testMost = dynamic_cast<void*>(ptr_d);
Base_Class* tptrDerived = dynamic_cast<Derived_Class*>(static_cast<Base_Class*>(testDerived));
tptrDerived->dummy();
Base_Class* tptrMost = dynamic_cast<Derived_Class*>(static_cast<Base_Class*>(testMost));
tptrMost->dummy();
//tptrMost = dynamic_cast<AnotherMostDerivedObject*>(static_cast<Base_Class*>(testMost));
//tptrMost->dummy(); //fails
} catch (exception& my_ex) {cout << "Exception: " << my_ex.what();}
system("pause");
return 0;
}
이것이 어떤 식 으로든 정확하지 않은 경우 저를 수정하십시오.
기억 장치를 메모리 풀에 다시 넣을 때 유용하지만 기본 클래스에 대한 포인터 만 유지합니다. 이 경우 원래 주소를 알아 내야합니다.
@BruceAdi의 답변을 확장 하고이 토론 에서 영감을 얻은 다음 포인터 조정이 필요할 수있는 다형성 상황이 있습니다. 다음과 같은 공장 유형 설정이 있다고 가정합니다.
struct Base { virtual ~Base() = default; /* ... */ };
struct Derived : Base { /* ... */ };
template <typename ...Args>
Base * Factory(Args &&... args)
{
return ::new Derived(std::forward<Args>(args)...);
}
template <typename ...Args>
Base * InplaceFactory(void * location, Args &&... args)
{
return ::new (location) Derived(std::forward<Args>(args)...);
}
이제 다음과 같이 말할 수 있습니다.
Base * p = Factory();
그러나 이것을 어떻게 수동으로 정리할까요? 호출 할 실제 메모리 주소가 필요합니다 ::operator delete
.
void * addr = dynamic_cast<void*>(p);
p->~Base(); // OK thanks to virtual destructor
// ::operator delete(p); // Error, wrong address!
::operator delete(addr); // OK
또는 메모리를 재사용 할 수 있습니다.
void * addr = dynamic_cast<void*>(p);
p->~Base();
p = InplaceFactory(addr, "some", "arguments");
delete p; // OK now
집에서 하지마
struct Base {
virtual ~Base ();
};
struct D : Base {};
Base *create () {
D *p = new D;
return p;
}
void *destroy1 (Base *b) {
void *p = dynamic_cast<void*> (b);
b->~Base ();
return p;
}
void destroy2 (void *p) {
operator delete (p);
}
int i = (destroy2 (destroy1 (create ())), i);
경고 : 다음과 같이 정의 된 경우 작동 하지 않습니다D
.
구조체 D : Base { void * operator new (size_t); 무효 연산자 삭제 (void *); };
작동시킬 방법이 없습니다.
This might be one way to provide an Opaque Pointer through an ABI. Opaque Pointers -- and, more generally, Opaque Data Types -- are used to pass objects and other resources around between library code and client code in such a way that the client code can be isolated from the implementation details of the library. There are other ways to accomplish this, to be sure, and maybe some of them would be better for a particular use case.
Windows makes a lot of use of Opaque Pointers in its API. HANDLE
is, I believe, generally an opaque pointer to the actual resource you have a HANDLE
to, for example. HANDLE
s can be Kernel Objects like files, GDI objects, and all sorts of User Objects of various kinds -- all of which must be vastly different in implementation, but all are returned as a HANDLE
to the user.
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
/*** LIBRARY.H ***/
namespace lib
{
typedef void* MYHANDLE;
void ShowObject(MYHANDLE h);
MYHANDLE CreateObject();
void DestroyObject(MYHANDLE);
};
/*** CLIENT CODE ***/
int main()
{
for( int i = 0; i < 25; ++i )
{
cout << "[" << setw(2) << i << "] :";
lib::MYHANDLE h = lib::CreateObject();
lib::ShowObject(h);
lib::DestroyObject(h);
cout << "\n";
}
}
/*** LIBRARY.CPP ***/
namespace impl
{
class Base { public: virtual ~Base() { cout << "[~Base]"; } };
class Foo : public Base { public: virtual ~Foo() { cout << "[~Foo]"; } };
class Bar : public Base { public: virtual ~Bar() { cout << "[~Bar]"; } };
};
lib::MYHANDLE lib::CreateObject()
{
static bool init = false;
if( !init )
{
srand((unsigned)time(0));
init = true;
}
if( rand() % 2 )
return static_cast<impl::Base*>(new impl::Foo);
else
return static_cast<impl::Base*>(new impl::Bar);
}
void lib::DestroyObject(lib::MYHANDLE h)
{
delete static_cast<impl::Base*>(h);
}
void lib::ShowObject(lib::MYHANDLE h)
{
impl::Foo* foo = dynamic_cast<impl::Foo*>(static_cast<impl::Base*>(h));
impl::Bar* bar = dynamic_cast<impl::Bar*>(static_cast<impl::Base*>(h));
if( foo )
cout << "FOO";
if( bar )
cout << "BAR";
}
'Program Tip' 카테고리의 다른 글
모든 git 명령에 드라 이런 옵션이 있습니까? (0) | 2020.10.27 |
---|---|
목록을 인스턴스화하는 방법 (0) | 2020.10.27 |
Swift : 호출에 인수 레이블 'xxx'가 없습니다. (0) | 2020.10.27 |
Gradle 작업 정의에서 그루비 구문 이해 (0) | 2020.10.27 |
XAML (.NET 4 Framework 이전)에서 제네릭 형식을 지정할 수 있습니까? (0) | 2020.10.27 |