IT TIP

어떤 C ++ 함정을 피해야합니까?

itqueen 2020. 10. 22. 23:47
반응형

어떤 C ++ 함정을 피해야합니까?


STL에서 벡터에 대해 처음 배웠던 것을 기억하고 얼마 후 프로젝트 중 하나에 bool 벡터를 사용하고 싶었습니다. 이상한 행동을보고 몇 가지 조사를 한 후, 저는 bools 벡터가 실제로 bools 벡터가 아니라는 것을 알게되었습니다 .

C ++에서 피해야 할 다른 일반적인 함정이 있습니까?


짧은 목록은 다음과 같습니다.

  • 공유 포인터를 사용하여 메모리 할당 및 정리를 관리하여 메모리 누수 방지
  • 사용 자원 수집되어 초기화 자원 정리를 관리하기 위해 (RAII) 관용구 - 특히 예외의 존재
  • 생성자에서 가상 함수 호출 방지
  • 가능한 경우 최소한의 코딩 기술을 사용합니다. 예를 들어 필요할 때만 변수를 선언하고, 변수 범위를 지정하고, 가능한 경우 초기 설계를 설계합니다.
  • 당신이 던지는 예외와 당신이 간접적으로 사용하고있는 클래스에 의해 던진 예외와 관련하여 당신의 코드에서 예외 처리를 진정으로 이해하십시오. 이것은 템플릿이있는 경우 특히 중요합니다.

RAII, 공유 포인터 및 미니멀 코딩은 물론 C ++에만 국한된 것은 아니지만 언어로 개발할 때 자주 발생하는 문제를 방지하는 데 도움이됩니다.

이 주제에 대한 훌륭한 책은 다음과 같습니다.

  • 효과적인 C ++-Scott Meyers
  • 보다 효과적인 C ++-Scott Meyers
  • C ++ 코딩 표준-Sutter 및 Alexandrescu
  • C ++ FAQ-Cline

이 책을 읽는 것은 당신이 요구하는 종류의 함정을 피하는 데 무엇보다 도움이되었습니다.


중요성 감소 순서의 함정

우선 수상 경력에 빛나는 C ++ FAQ를 방문해야합니다 . 함정에 대한 많은 좋은 답변이 있습니다. 추가 문의 사항이있는 경우, 방문 ##c++irc.freenode.org에서 IRC . 가능하다면 기꺼이 도와 드리겠습니다. 다음의 모든 함정은 원래 작성되었습니다. 무작위 소스에서 복사 된 것이 아닙니다.


delete[]on new, deleteonnew[]

솔루션 : 위의 작업을 수행하면 정의되지 않은 동작이 발생합니다. 모든 일이 발생할 수 있습니다. 코드와 그것이 무엇을, 항상 이해 delete[]무엇을 new[]하고, delete무엇을 new, 그 다음은 일어나지 않을 것입니다.

예외 :

typedef T type[N]; T * pT = new type; delete[] pT;

당신은 할 필요 delete[]조차 비록 new당신이 배열을 new'ed 때문에. 따라서을 (를) 사용하는 경우 typedef특별히주의하십시오.


생성자 또는 소멸자에서 가상 함수 호출

솔루션 : 가상 함수를 호출해도 파생 클래스의 재정의 함수가 호출되지 않습니다. 생성자 또는 desctructor에서 순수 가상 함수호출하는 것은 정의되지 않은 동작입니다.


이미 삭제 된 포인터를 호출 delete하거나delete[]

솔루션 : 삭제하는 모든 포인터에 0을 할당하십시오. delete또는 delete[]널 포인터를 호출 하면 아무 작업도 수행되지 않습니다.


'배열'의 요소 수를 계산할 때 포인터의 크기를 사용합니다.

솔루션 : 함수에 포인터로 배열을 전달해야 할 때 포인터와 함께 요소 수를 전달하십시오. 실제로 배열이어야하는 배열의 크기를 취한다면 여기에서 제안한 함수를 사용하십시오 .


포인터 인 것처럼 배열 사용. 따라서 T **2 차원 배열에 사용합니다.

솔루션 : 차이점과 처리 방법은 여기참조 하십시오 .


문자열 리터럴에 쓰기 : char * c = "hello"; *c = 'B';

솔루션 : 문자열 리터럴의 데이터에서 초기화 된 배열을 할당하면 다음과 같이 쓸 수 있습니다.

char c[] = "hello"; *c = 'B';

문자열 리터럴에 쓰는 것은 정의되지 않은 동작입니다. 어쨌든 위의 문자열 리터럴에서 로의 변환 char *은 더 이상 사용되지 않습니다. 따라서 컴파일러는 경고 수준을 높이면 경고 할 것입니다.


리소스를 생성 한 다음 무언가가 던져 질 때 해제하는 것을 잊었습니다.

해결 방법 : 같은 사용 스마트 포인터 std::unique_ptr또는 std::shared_ptr다른 답변으로 지적.


이 예제와 같이 객체를 두 번 수정합니다. i = ++i;

솔루션 : 위 i의 값을 i+1. 그러나 그것이하는 일은 정의되지 않았습니다. i결과 를 증가 시키고 할당하는 대신 i오른쪽에서도 변경 됩니다. 두 시퀀스 지점간에 개체를 변경하는 것은 정의되지 않은 동작입니다. 시퀀스 포인트는 ||, &&, comma-operator, semicolonentering a function(비 완전한 목록을!). 올바르게 작동하도록 코드를 다음과 같이 변경하십시오.i = i + 1;


기타 문제

와 같은 차단 함수를 호출하기 전에 스트림을 플러시하는 것을 잊었습니다 sleep.

솔루션 : std::endl대신 \n또는을 호출 하여 스트리밍하여 스트림을 플러시합니다 stream.flush();.


변수 대신 함수 선언.

솔루션 : 컴파일러가 예를 들어 해석하기 때문에 문제가 발생합니다.

Type t(other_type(value));

호출하는 유형의 매개 변수를 t반환 Type하고 갖는 함수의 함수 선언으로 . 첫 번째 인수를 괄호로 묶어 해결합니다. 이제 유형 의 변수를 얻습니다 .other_typevaluetType

Type t((other_type(value)));

현재 번역 단위 ( .cpp파일) 에서만 선언 된 자유 객체의 함수를 호출합니다 .

솔루션 : 표준은 다른 번역 단위에 정의 된 자유 객체 (네임 스페이스 범위에서) 생성 순서를 정의하지 않습니다. 아직 생성되지 않은 개체에서 멤버 함수를 호출하는 것은 정의되지 않은 동작입니다. 대신 객체의 번역 단위에서 다음 함수를 정의하고 다른 함수에서 호출 할 수 있습니다.

House & getTheHouse() { static House h; return h; }

그러면 요청시 개체가 생성되고 함수를 호출 할 때 완전히 구성된 개체가 남게됩니다.


.cpp다른 .cpp파일 에서 사용되는 동안 파일 에서 템플릿을 정의 합니다.

솔루션 : 거의 항상 undefined reference to .... 모든 템플릿 정의를 헤더에 넣으면 컴파일러가이를 사용할 때 필요한 코드를 이미 생성 할 수 있습니다.


static_cast<Derived*>(base);base가의 가상 기본 클래스에 대한 포인터 인 경우 Derived.

솔루션 : 가상베이스 클래스는 상속 트리에서 간접적으로 여러 클래스에 의해 두 번 이상 상속 되더라도 한 번만 발생하는베이스입니다. 위의 행위는 표준에 의해 허용되지 않습니다. 이를 위해 dynamic_cast를 사용하고 기본 클래스가 다형성인지 확인하십시오.


dynamic_cast<Derived*>(ptr_to_base); 염기가 다형성이 아닌 경우

솔루션 : 표준은 전달 된 객체가 다형성이 아닐 때 포인터 또는 참조의 다운 캐스트를 허용하지 않습니다. 이 클래스 또는 기본 클래스 중 하나에 가상 기능이 있어야합니다.


함수를 받아들이도록 만들기 T const **

솔루션 :을 사용하는 것보다 안전하다고 생각할 수 T **있지만 실제로 통과하려는 사람들에게 두통을 유발할 T**수 있습니다. 표준에서는 허용하지 않습니다. 허용되지 않는 이유에 대한 깔끔한 예를 제공합니다.

int main() {
    char const c = ’c’;
    char* pc;
    char const** pcc = &pc; //1: not allowed
    *pcc = &c;
    *pc = ’C’; //2: modifies a const object
}

T const* const*;대신 항상 수락하십시오 .

C ++에 대한 또 다른 (닫힌) 함정 스레드이므로 찾는 사람들이 찾을 수 있습니다. Stack Overflow question C ++ pitfalls 입니다.


일부는 일반적인 C ++ 함정을 피하는 데 도움이되는 C ++ 책이 있어야합니다.

효과적인 C ++
보다 효과적인 C ++
효과적인 STL

효과적인 STL 책은 bools 문제의 벡터를 설명합니다. :)


Brian은 훌륭한 목록을 가지고 있습니다. "항상 단일 인수 생성자를 명시 적으로 표시 (자동 캐스팅을 원하는 드문 경우 제외)"를 추가합니다.


특정 팁이 아니라 일반적인 지침 : 출처를 확인하십시오. C ++는 오래된 언어이며 수년에 걸쳐 많이 변경되었습니다. 모범 사례가 변경되었지만 불행히도 여전히 많은 오래된 정보가 있습니다. 여기에 아주 좋은 책 추천이 몇 가지 있습니다. Scott Meyers C ++ 책을 한 권씩 구입할 수 있습니다. Boost와 Boost에서 사용되는 코딩 스타일에 익숙해 지십시오. 해당 프로젝트에 참여한 사람들은 C ++ 디자인의 최첨단에 있습니다.

바퀴를 재발 명하지 마십시오. STL 및 Boost에 익숙해지고 가능하면 자신의 기능을 사용하십시오. 특히, 아주 좋은 이유가 없다면 STL 문자열과 컬렉션을 사용하십시오. auto_ptr 및 Boost 스마트 포인터 라이브러리에 대해 잘 알고, 각 유형의 스마트 포인터가 사용되는 상황을 이해 한 다음 원시 포인터를 사용했을 수있는 모든 곳에서 스마트 포인터를 사용하십시오. 당신의 코드는 효율적이고 메모리 누수에 덜 취약합니다.

C 스타일 캐스트 대신 static_cast, dynamic_cast, const_cast 및 reinterpret_cast를 사용하십시오. C 스타일의 캐스트와 달리 그들은 당신이 요구하고 있다고 생각하는 것과 다른 유형의 캐스트를 정말로 요구하고 있는지 알려줄 것입니다. 그리고 그들은 캐스트가 일어나고 있음을 독자에게 경고하면서 viisually 눈에.니다.


Scott Wheeler 의 웹 페이지 C ++ Pitfalls 에서는 몇 가지 주요 C ++ 함정을 다룹니다.


내가 어려운 방법을 배우지 않았 으면하는 두 가지 문제점 :

(1) 많은 출력 (예 : printf)이 기본적으로 버퍼링됩니다. 충돌 코드를 디버깅하고 버퍼링 된 디버그 문을 사용하는 경우 마지막 출력이 실제로 코드에서 발생한 마지막 인쇄 문 아닐 수 있습니다. 해결책은 각 디버그 인쇄 후 버퍼를 플러시하거나 버퍼링을 완전히 끄는 것입니다.

(2) 초기화에주의하십시오-(a) 전역 / 정적으로서 클래스 인스턴스를 피하십시오; (b) 포인터에 대한 NULL과 같은 사소한 값이더라도 모든 멤버 변수를 ctor에서 안전한 값으로 초기화하십시오.

추론 : 전역 객체 초기화의 순서가 보장되지 않으므로 (전역에는 정적 변수가 포함됨) 객체 Y 이전에 초기화되는 객체 X에 의존하기 때문에 비 결정적으로 실패하는 것처럼 보이는 코드로 끝날 수 있습니다. 멤버 bool 또는 클래스의 열거 형과 같은 기본 유형 변수는 놀라운 상황에서 다른 값으로 끝날 것입니다. 다시 말하지만 동작은 매우 비 결정적으로 보일 수 있습니다.


이미 몇 번 언급했지만 Scott Meyers의 저서 Effective C ++Effective STL 은 C ++를 지원하는 데있어 금상 가치가 있습니다.

생각해 보면 Steven Dewhurst의 C ++ Gotchas 는 훌륭한 "from the trenches"리소스이기도합니다. 자신의 예외를 롤링하는 그의 항목과 그것들이 어떻게 구성되어야하는지에 대한 그의 항목은 하나의 프로젝트에서 정말 도움이되었습니다.


C와 같은 C ++ 사용. 코드에 생성 및 릴리스주기가 있습니다.

C ++에서는 예외 안전이 아니므로 릴리스가 실행되지 않을 수 있습니다. C ++에서는 이 문제를 해결하기 위해 RAII사용 합니다.

수동 생성 및 해제가있는 모든 리소스는 개체에 래핑되어야 이러한 작업이 생성자 / 소멸자에서 수행됩니다.

// C Code
void myFunc()
{
    Plop*   plop = createMyPlopResource();

    // Use the plop

    releaseMyPlopResource(plop);
}

C ++에서는 다음과 같이 객체로 래핑되어야합니다.

// C++
class PlopResource
{
    public:
        PlopResource()
        {
            mPlop=createMyPlopResource();
            // handle exceptions and errors.
        }
        ~PlopResource()
        {
             releaseMyPlopResource(mPlop);
        }
    private:
        Plop*  mPlop;
 };

void myFunc()
{
    PlopResource  plop;

    // Use the plop
    // Exception safe release on exit.
}

C ++ Gotchas 가 유용 할 수 있습니다.


여기에 내가 빠질 불행한 구덩이가 있습니다. 이 모든 것들은 나를 놀라게 한 행동에 물린 후에야 이해할 수있는 좋은 이유가 있습니다.

  • virtual생성자 함수는 없습니다 .

  • ODR (One Definition Rule)을 위반하지 마십시오. 이것이 바로 익명 네임 스페이스의 목적입니다.

  • 멤버 초기화 순서는 선언 된 순서에 따라 다릅니다.

    class bar {
        vector<int> vec_;
        unsigned size_; // Note size_ declared *after* vec_
    public:
        bar(unsigned size)
            : size_(size)
            , vec_(size_) // size_ is uninitialized
            {}
    };
    
  • 기본값이며 virtual의미가 다릅니다.

    class base {
    public:
        virtual foo(int i = 42) { cout << "base " << i; }
    };
    
    class derived : public base {
    public:
        virtual foo(int i = 12) { cout << "derived "<< i; }
    };
    
    derived d;
    base& b = d;
    b.foo(); // Outputs `derived 42`
    

초보 개발자에게 가장 중요한 함정은 C와 C ++ 간의 혼동을 피하는 것입니다. C ++는 클래스가있는 더 나은 C 또는 C로 취급되어서는 안됩니다. 이것은 그 능력을 제거하고 심지어 위험하게 만들 수 있기 때문입니다 (특히 C에서 메모리를 사용할 때).


Check out boost.org. It provides a lot of additional functionality, especially their smart pointer implementations.


PRQA have an excellent and free C++ coding standard based on books from Scott Meyers, Bjarne Stroustrop and Herb Sutter. It brings all this information together in one document.


  1. Not reading the C++ FAQ Lite. It explains many bad (and good!) practices.
  2. Not using Boost. You'll save yourself a lot of frustration by taking advantage of Boost where possible.

Be careful when using smart pointers and container classes.


Avoid pseudo classes and quasi classes... Overdesign basically.


Forgetting to define a base class destructor virtual. This means that calling delete on a Base* won't end up destructing the derived part.


Keep the name spaces straight (including struct, class, namespace, and using). That's my number-one frustration when the program just doesn't compile.


To mess up, use straight pointers a lot. Instead, use RAII for almost anything, making sure of course that you use the right smart pointers. If you write "delete" anywhere outside a handle or pointer-type class, you're very likely doing it wrong.


Read the book C++ Gotchas: Avoiding Common Problems in Coding and Design.


  • Blizpasta. That's a huge one I see a lot...

  • Uninitialized variables are a huge mistake that students of mine make. A lot of Java folks forget that just saying "int counter" doesn't set counter to 0. Since you have to define variables in the h file (and initialize them in the constructor/setup of an object), it's easy to forget.

  • Off-by-one errors on for loops / array access.

  • Not properly cleaning object code when voodoo starts.


  • static_cast downcast on a virtual base class

Not really... Now about my misconception: I thought that A in the following was a virtual base class when in fact it's not; it's, according to 10.3.1, a polymorphic class. Using static_cast here seems to be fine.

struct B { virtual ~B() {} };

struct D : B { };

In summary, yes, this is a dangerous pitfall.


Always check a pointer before you dereference it. In C, you could usually count on a crash at the point where you dereference a bad pointer; in C++, you can create an invalid reference which will crash at a spot far removed from the source of the problem.

class SomeClass
{
    ...
    void DoSomething()
    {
        ++counter;    // crash here!
    }
    int counter;
};

void Foo(SomeClass & ref)
{
    ...
    ref.DoSomething();    // if DoSomething is virtual, you might crash here
    ...
}

void Bar(SomeClass * ptr)
{
    Foo(*ptr);    // if ptr is NULL, you have created an invalid reference
                  // which probably WILL NOT crash here
}

Forgetting an & and thereby creating a copy instead of a reference.

This happened to me twice in different ways:

  • One instance was in an argument list, which caused a large object to be put on the stack with the result of a stack overflow and crash of the embedded system.

  • I forgot the & on an instance variable, with the effect that the object was copied. After registering as a listener to the copy I wondered why I never got the callbacks from the original object.

Both where rather hard to spot, because the difference is small and hard to see, and otherwise objects and references are used syntactically in the same way.


Intention is (x == 10):

if (x = 10) {
    //Do something
}

I thought I would never make this mistake myself, but I actually did it recently.


The essay/article Pointers, references and Values is very useful. It talks avoid avoiding pitfalls and good practices. You can browse the whole site too, which contains programming tips, mainly for C++.


I spent many years doing C++ development. I wrote a quick summary of problems I had with it years ago. Standards-compliant compilers are not really a problem anymore, but I suspect the other pitfalls outlined are still valid.


#include <boost/shared_ptr.hpp>
class A {
public:
  void nuke() {
     boost::shared_ptr<A> (this);
  }
};

int main(int argc, char** argv) {
  A a;
  a.nuke();
  return(0);
}

참고 URL : https://stackoverflow.com/questions/30373/what-c-pitfalls-should-i-avoid

반응형