문제

간단한 'C' 구조를 초기화하는 것을 기억하는 대신 다음과 같이 이 구조에서 파생하여 생성자에서 0으로 만들 수 있습니다.

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct()
    {
        memset(this, 0, sizeof(MY_STRUCT));
    }
};

이 트릭은 Win32 구조를 초기화하는 데 자주 사용되며 때로는 유비쿼터스 구조를 설정할 수 있습니다. cb크기 회원.

이제, 파괴할 memset 호출에 대한 가상 함수 테이블이 없는 한 이것이 안전한 방법입니까?

도움이 되었습니까?

해결책

전문:

내 대답은 여전히 ​​Ok입니다. Litb의 답변 나보다 훨씬 우월한 이유는:

  1. 내가 몰랐던 비법을 가르쳐준다. (litb의 답변은 대개 이런 효과가 있지만, 적어보는 것은 이번이 처음이다)
  2. 질문에 정확히 답합니다(즉, 원래 구조체의 부분을 0으로 초기화).

그러니 내 답변보다 먼저 litb의 답변을 고려하십시오.사실, 나는 질문 작성자에게 litb의 답변을 올바른 것으로 간주할 것을 제안합니다.

원래 답변

실제 물체를 두는 것(예:표준::문자열) 등실제 객체는 memset 이전에 초기화된 다음 0으로 덮어쓰기 때문에 내부가 중단됩니다.

초기화 목록을 사용하는 것은 g++에서 작동하지 않습니다(놀랍네요...).대신 CMyStruct 생성자 본문에서 초기화하세요.C++ 친화적입니다.

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct() { n1 = 0 ; n2 = 0 ; }
};

추신.:나는 당신이 가지고 있다고 생각 아니요 물론 MY_STRUCT를 제어할 수 있습니다.제어를 사용하면 MY_STRUCT 내부에 생성자를 직접 추가하고 상속에 대해서는 잊어버렸을 것입니다.C와 유사한 구조체에 가상이 아닌 메서드를 추가하고 여전히 구조체로 동작하도록 할 수 있습니다.

편집하다:Lou Franco의 의견 뒤에 누락된 괄호를 추가했습니다.감사해요!

편집 2 :g++에서 코드를 시도했는데 어떤 이유로 초기화 목록을 사용하는 것이 작동하지 않습니다.본문 생성자를 사용하여 코드를 수정했습니다.하지만 해결책은 여전히 ​​유효합니다.

원래 코드가 변경되었으므로 내 게시물을 재평가해 주세요(자세한 내용은 변경 로그 참조).

편집 3 :Rob의 의견을 읽은 후 그는 토론할 가치가 있는 요점을 갖고 있다고 생각합니다."동의합니다. 하지만 이것은 새로운 SDK로 변경될 수 있는 거대한 Win32 구조일 수 있으므로 memset은 미래에도 사용할 수 있습니다."

나는 동의하지 않는다:Microsoft를 알면 완벽한 이전 버전과의 호환성이 필요하기 때문에 변하지 않을 것입니다.대신 확장된 MY_STRUCT를 생성합니다. MY_STRUCT와 동일한 초기 레이아웃을 갖고 끝에 추가 멤버가 있으며 RegisterWindow, IIRC에 사용되는 구조체와 같은 "크기" 멤버 변수를 통해 인식할 수 있는 구조체입니다.

따라서 Rob의 의견에서 유일하게 유효한 점은 "거대한" 구조체입니다.이 경우 memset이 더 편리할 수 있지만 MY_STRUCT를 상속하는 대신 CMyStruct의 변수 멤버로 만들어야 합니다.

또 다른 해킹이 보이지만 구조체 정렬 문제로 인해 이것이 중단될 것 같습니다.

편집 4:Frank Krueger의 솔루션을 살펴보십시오.이식 가능하다고 약속할 수는 없지만(그렇다고 생각합니다), C++에서 "this" 포인터 "주소"가 기본 클래스에서 상속된 클래스로 이동하는 사례를 보여주기 때문에 기술적인 관점에서 여전히 흥미롭습니다. .

다른 팁

간단히 베이스의 값을 초기화하면 모든 멤버가 0으로 처리됩니다.이것은 보장됩니다

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct():MY_STRUCT() { }
};

이것이 작동하려면 예제와 같이 기본 클래스에 사용자 선언 생성자가 없어야 합니다.

아니 불쾌한 memset 그에 대한.그렇다고 보장되는 건 아니다 memset 실제로는 작동해야 하지만 코드에서는 작동합니다.

memset보다 훨씬 더 나은 점은 다음과 같은 작은 트릭을 대신 사용할 수 있다는 것입니다.

MY_STRUCT foo = { 0 };

이렇게 하면 모든 멤버가 0(또는 기본값 iirc)으로 초기화되므로 각각에 대해 값을 지정할 필요가 없습니다.

이렇게 하면 문제가 발생하더라도 작동해야 하므로 훨씬 더 안전하다고 느낄 것입니다. vtable (또는 컴파일러가 비명을 지를 것입니다).

memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));

귀하의 솔루션이 효과가 있을 것이라고 확신하지만 혼합할 때 보장될 수 있는 것이 있는지 의심됩니다. memset 그리고 수업.

이것은 C 관용구를 C++로 이식하는 완벽한 예입니다(그리고 왜 항상 작동하지 않을 수도 있는지...).

memset을 사용할 때 발생하는 문제는 C++에서 구조체와 클래스가 기본적으로 구조체에 공개 가시성이 있고 클래스에 비공개 가시성이 있다는 점을 제외하면 정확히 동일하다는 것입니다.

따라서 나중에 일부 선의의 프로그래머가 MY_STRUCT를 다음과 같이 변경하면 어떻게 될까요?


struct MY_STRUCT
{
    int n1;
    int n2;

   // Provide a default implementation...
   virtual int add() {return n1 + n2;}  
};

해당 단일 기능을 추가하면 memset이 혼란을 일으킬 수 있습니다.에 자세한 논의가 있습니다. comp.lang.c+

예제에는 "지정되지 않은 동작"이 있습니다.

POD가 아닌 경우 컴파일러가 객체(모든 기본 클래스 및 멤버)를 배치하는 순서는 지정되지 않습니다(ISO C++ 10/3).다음을 고려하세요:

struct A {
  int i;
};

class B : public A {       // 'B' is not a POD
public:
  B ();

private:
  int j;
};

이는 다음과 같이 구성될 수 있습니다.

[ int i ][ int j ]

또는 다음과 같습니다:

[ int j ][ int i ]

따라서 'this' 주소에 직접 memset을 사용하는 것은 매우 불특정 동작입니다.위의 답변 중 하나는 언뜻 보기에 더 안전해 보입니다.

 memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));

그러나 엄밀히 말하면 이것 역시 불특정 행위를 초래한다고 믿습니다.규범적인 텍스트를 찾을 수 없지만 10/5의 메모에는 다음과 같이 나와 있습니다."기본 클래스 하위 개체는 동일한 유형의 가장 많이 파생된 개체의 레이아웃과 다른 레이아웃(3.7)을 가질 수 있습니다."

결과적으로 I 컴파일러는 다양한 멤버를 사용하여 공간 최적화를 수행할 수 있습니다.

struct A {
  char c1;
};

struct B {
  char c2;
  char c3;
  char c4;
  int i;
};

class C : public A, public B
{
public:
  C () 
  :  c1 (10);
  {
    memset(static_cast<B*>(this), 0, sizeof(B));      
  }
};

다음과 같이 배치할 수 있습니다.

[ char c1 ] [ char c2, char c3, char c4, int i ]

32비트 시스템에서는 정렬 등으로 인해'B'의 경우 sizeof(B)는 8바이트일 가능성이 높습니다.그러나 컴파일러가 데이터 멤버를 압축하는 경우 sizeof(C)는 '8' 바이트일 수도 있습니다.따라서 memset를 호출하면 'c1'에 지정된 값을 덮어쓸 수 있습니다.

C++에서는 클래스나 구조체의 정확한 레이아웃이 보장되지 않습니다. 따라서 외부에서 크기에 대해 가정하면 안 됩니다(컴파일러가 아닌 경우).

아마도 작동하지 않는 컴파일러를 찾거나 혼합에 vtable을 추가할 때까지 작동할 것입니다.

이미 생성자가 있다면 n1=0으로 초기화하는 것이 어떨까요?n2=0;-- 확실히 그게 더 중요해 정상 방법.

편집하다:실제로 paercebal에서 알 수 있듯이 ctor 초기화가 훨씬 더 좋습니다.

내 의견은 '아니요'입니다.나는 그것이 무엇을 얻는 지 잘 모르겠습니다.

CMyStruct의 정의가 변경되고 멤버를 추가/삭제하면 버그가 발생할 수 있습니다.용이하게.

매개변수가 있는 MyStruct를 사용하는 CMyStruct에 대한 생성자를 만듭니다.

CMyStruct::CMyStruct(MyStruct &)

아니면 그런 것을 추구했습니다.그런 다음 공개 또는 비공개 'MyStruct' 멤버를 초기화할 수 있습니다.

ISO C++ 관점에서 볼 때 다음과 같은 두 가지 문제가 있습니다.

(1) 객체가 POD입니까?약어는 Plain Old Data의 약자이며 표준은 POD에 포함할 수 없는 항목을 열거합니다(Wikipedia에 좋은 요약이 있습니다).POD가 아니면 memset을 설정할 수 없습니다.

(2) 모든 비트가 0으로 유효하지 않은 멤버가 있습니까?Windows 및 Unix에서 NULL 포인터는 모두 비트 0입니다.그럴 필요는 없습니다.부동 소수점 0은 매우 일반적인 IEEE754와 x86에서 모든 비트가 0입니다.

Frank Kruegers 팁은 memset을 POD가 아닌 클래스의 POD 기반으로 제한하여 문제를 해결합니다.

이것을 시도하십시오 - 새로운 것을 과부하하십시오.

편집하다:추가해야겠네요 - 이전에 메모리가 0이 되기 때문에 안전합니다. 어느 생성자가 호출됩니다.큰 결함 - 객체가 동적으로 할당된 경우에만 작동합니다.

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct()
    {
        // whatever
    }
    void* new(size_t size)
    {
        // dangerous
        return memset(malloc(size),0,size);
        // better
        if (void *p = malloc(size))
        {
            return (memset(p, 0, size));
        }
        else
        {
            throw bad_alloc();
        }
    }
    void delete(void *p, size_t size)
    {
        free(p);
    }

};

MY_STRUCT가 코드이고 C++ 컴파일러를 사용하는 것이 만족스러우면 클래스를 래핑하지 않고도 생성자를 넣을 수 있습니다.

struct MY_STRUCT
{
  int n1;
  int n2;
  MY_STRUCT(): n1(0), n2(0) {}
};

효율성에 대해서는 잘 모르겠지만 효율성이 필요하다는 것을 증명하지 않았을 때 속임수를 쓰는 것을 싫어합니다.

댓글 달기 Litb의 답변 (아직 직접 댓글을 달 수 없는 것 같습니다):

이 멋진 C++ 스타일 솔루션을 사용하더라도 이를 POD가 아닌 멤버가 포함된 구조체에 순진하게 적용하지 않도록 매우 주의해야 합니다.

그러면 일부 컴파일러는 더 이상 올바르게 초기화되지 않습니다.

보다 비슷한 질문에 대한 이 답변.저는 개인적으로 추가 std::string이 포함된 VC2008에서 나쁜 경험을 했습니다.

내가 하는 일은 집계 초기화를 사용하는 것이지만 내가 관심 있는 멤버에 대해서만 초기화를 지정하는 것입니다. 예:

STARTUPINFO si = {
    sizeof si,      /*cb*/
    0,              /*lpReserved*/
    0,              /*lpDesktop*/
    "my window"     /*lpTitle*/
};

나머지 멤버는 (Drealmer의 게시물에서와 같이) 적절한 유형의 0으로 초기화됩니다.여기서는 Microsoft가 중간에 새 구조 멤버를 추가하여 호환성을 불필요하게 중단하지 않을 것이라고 신뢰하고 있습니다(합리적인 가정).이 솔루션은 나에게 최적이라고 생각합니다. 하나의 명령문, 클래스 없음, memset 없음, 부동 소수점 0 또는 널 포인터의 내부 표현에 대한 가정 없음.

상속과 관련된 해킹은 끔찍한 스타일이라고 생각합니다.공개 상속은 대부분의 독자에게 IS-A를 의미합니다.또한 기본으로 설계되지 않은 클래스에서 상속하고 있다는 점도 참고하세요.가상 소멸자가 없기 때문에 base에 대한 포인터를 통해 파생 클래스 인스턴스를 삭제하는 클라이언트는 정의되지 않은 동작을 호출합니다.

나는 구조가 귀하에게 제공되며 수정할 수 없다고 가정합니다.구조를 변경할 수 있다면 확실한 해결책은 생성자를 추가하는 것입니다.

원하는 것이 구조를 초기화하는 간단한 매크로뿐이라면 C++ 래퍼로 코드를 과도하게 엔지니어링하지 마십시오.

#include <stdio.h>

#define MY_STRUCT(x) MY_STRUCT x = {0}

struct MY_STRUCT
{
    int n1;
    int n2;
};

int main(int argc, char *argv[])
{
    MY_STRUCT(s);

    printf("n1(%d),n2(%d)\n", s.n1, s.n2);

    return 0;
}

약간의 코드이지만 재사용이 가능합니다.한 번만 포함하면 모든 POD에서 작동합니다.이 클래스의 인스턴스를 MY_STRUCT가 필요한 함수에 전달하거나 GetPointer 함수를 사용하여 구조를 수정하는 함수에 전달할 수 있습니다.

template <typename STR>
class CStructWrapper
{
private:
    STR MyStruct;

public:
    CStructWrapper() { STR temp = {}; MyStruct = temp;}
    CStructWrapper(const STR &myStruct) : MyStruct(myStruct) {}

    operator STR &() { return MyStruct; }
    operator const STR &() const { return MyStruct; }

    STR *GetPointer() { return &MyStruct; }
};

CStructWrapper<MY_STRUCT> myStruct;
CStructWrapper<ANOTHER_STRUCT> anotherStruct;

이렇게 하면 NULL이 모두 0인지 아니면 부동 소수점 표현인지 걱정할 필요가 없습니다.STR이 단순 집계 유형인 한 모든 것이 작동합니다.STR이 단순 집계 유형이 아닌 경우 컴파일 시간 오류가 발생하므로 실수로 이를 오용하는 것에 대해 걱정할 필요가 없습니다.또한 유형에 더 복잡한 것이 포함되어 있으면 기본 생성자가 있는 한 괜찮습니다.

struct MY_STRUCT2
{
    int n1;
    std::string s1;
};

CStructWrapper<MY_STRUCT2> myStruct2; // n1 is set to 0, s1 is set to "";

단점은 추가 임시 복사본을 만들기 때문에 속도가 느리고 컴파일러는 하나의 memset 대신 각 멤버를 개별적으로 0에 할당합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top