질문자 :gEdringer
저는 Java 배경에서 왔으며 C++에서 개체 작업을 시작했습니다. 그러나 나에게 일어난 한 가지 사실은 사람들이 객체 자체가 아닌 객체에 대한 포인터를 자주 사용한다는 것입니다. 예를 들어 다음 선언은 다음과 같습니다.
Object *myObject = new Object;
보다는:
Object myObject;
또는 함수를 사용하는 대신 다음과 같이 testFunc()
myObject.testFunc();
우리는 작성해야 합니다:
myObject->testFunc();
하지만 우리가 왜 이런 식으로 해야 하는지 이해할 수 없습니다. 메모리 주소에 직접 액세스할 수 있기 때문에 효율성과 속도와 관련이 있다고 가정합니다. 내가 맞아?
동적 할당을 너무 자주 보는 것은 매우 불행한 일입니다. 그것은 단지 얼마나 많은 나쁜 C++ 프로그래머가 있는지를 보여줍니다.
어떤 의미에서는 두 가지 질문이 하나로 묶여 있습니다. 첫 번째는 동적 할당( new
사용)을 언제 사용해야 합니까? 두 번째는 포인터를 언제 사용해야 합니까?
중요한 메시지는 항상 작업에 적절한 도구를 사용해야 한다는 것입니다. 거의 모든 상황에서 수동 동적 할당을 수행하거나 원시 포인터를 사용하는 것보다 더 적절하고 안전한 것이 있습니다.
동적 할당
귀하의 질문에서 당신은 객체를 생성하는 두 가지 방법을 보여주었습니다. 주요 차이점은 개체의 저장 기간입니다. Object myObject;
할 때; 블록 내에서 객체는 자동 저장 기간으로 생성됩니다. 즉, 범위를 벗어나면 자동으로 소멸됩니다. new Object()
를 수행하면 객체에 동적 저장 기간이 있으므로 명시적으로 delete
때까지 객체가 유지됩니다. 필요할 때만 동적 저장 기간을 사용해야 합니다. 즉, 가능 하면 항상 자동 저장 기간으로 개체를 만드는 것을 선호해야 합니다 .
동적 할당이 필요할 수 있는 두 가지 주요 상황은 다음과 같습니다.
- 현재 범위보다 오래 지속하려면 개체가 필요합니다. 해당 개체 는 복사본이 아니라 특정 메모리 위치에 있습니다. 개체를 복사/이동하는 데 문제가 없는 경우(대부분 그렇게 해야 함) 자동 개체를 선호해야 합니다.
- 스택을 쉽게 채울 수 있는 많은 메모리를 할당해야 합니다. 우리가 이것에 대해 걱정할 필요가 없다면 좋을 것입니다(대부분의 경우 그렇게 해서는 안 됨). 왜냐하면 그것은 실제로 C++의 범위 밖에 있기 때문입니다. 그러나 불행하게도 우리는 시스템의 현실을 다루어야 합니다. 위해 개발하고 있습니다.
동적 할당이 절대적으로 필요한 경우 스마트 포인터 또는 RAII 를 수행하는 다른 유형(예: 표준 컨테이너)으로 이를 캡슐화해야 합니다. 스마트 포인터는 동적으로 할당된 개체의 소유권 의미를 제공합니다. std::unique_ptr
및 std::shared_ptr
을 살펴보십시오. 적절하게 사용하면 자체 메모리 관리를 수행하는 것을 거의 완전히 피할 수 있습니다( 0 의 법칙 참조).
포인터
그러나 동적 할당 외에 원시 포인터에 대한 다른 일반적인 용도가 있지만 대부분은 선호해야 하는 대안이 있습니다. 이전과 마찬가지로 포인터가 실제로 필요한 경우가 아니면 항상 대안을 선호합니다 .
참조 의미 체계가 필요합니다 . 때때로 포인터를 사용하여 객체를 전달하고 싶을 때가 있습니다(할당된 방법에 관계없이). 전달하는 함수가 해당 특정 객체(복사본이 아님)에 액세스할 수 있기를 원하기 때문입니다. 그러나 대부분의 경우 포인터보다 참조 유형을 선호해야 합니다. 이것이 특별히 설계된 목적이기 때문입니다. 이것은 위의 상황 1에서와 같이 현재 범위를 넘어 객체의 수명을 반드시 연장하는 것과 관련이 없습니다. 이전과 마찬가지로 개체의 복사본을 전달해도 괜찮다면 참조 의미 체계가 필요하지 않습니다.
다형성이 필요합니다 . 객체에 대한 포인터나 참조를 통해서만 함수를 다형적으로(즉, 객체의 동적 유형에 따라) 호출할 수 있습니다. 그것이 필요한 동작이라면 포인터나 참조를 사용해야 합니다. 다시 말하지만, 참조가 우선되어야 합니다.
개체가 생략될 때 nullptr
이 전달되도록 허용 하여 개체가 선택 사항임을 나타내려고 합니다. 인수인 경우 기본 인수 또는 함수 오버로드를 사용하는 것이 좋습니다. std::optional
(C++17에 도입됨 - 이전 C++ 표준에서는 boost::optional
)과 같이 이 동작을 캡슐화하는 유형을 사용하는 것이 좋습니다.
컴파일 시간을 개선하기 위해 컴파일 단위를 분리하려고 합니다 . 포인터의 유용한 속성은 가리키는 유형의 전방 선언만 필요하다는 것입니다(실제로 개체를 사용하려면 정의가 필요합니다). 이를 통해 컴파일 프로세스의 일부를 분리할 수 있으므로 컴파일 시간이 크게 향상될 수 있습니다. Pimpl 관용구를 참조하십시오.
C 라이브러리 또는 C 스타일 라이브러리 와 인터페이스해야 합니다. 이 시점에서 원시 포인터를 사용해야 합니다. 당신이 할 수 있는 최선의 방법은 당신의 원시 포인터가 가능한 마지막 순간에만 느슨해지도록 하는 것입니다. get
멤버 함수를 사용하여 스마트 포인터에서 원시 포인터를 가져올 수 있습니다. 라이브러리가 핸들을 통해 할당을 해제할 것으로 예상하는 일부 할당을 수행하는 경우 개체를 적절하게 할당 해제하는 사용자 지정 삭제기로 핸들을 스마트 포인터로 감쌀 수 있습니다.
Community Wiki포인터에 대한 많은 사용 사례가 있습니다.
다형성 동작 . 다형성 유형의 경우 슬라이싱을 피하기 위해 포인터(또는 참조)가 사용됩니다.
class Base { ... }; class Derived : public Base { ... }; void fun(Base b) { ... } void gun(Base* b) { ... } void hun(Base& b) { ... } Derived d; fun(d); // oops, all Derived parts silently "sliced" off gun(&d); // OK, a Derived object IS-A Base object hun(d); // also OK, reference also doesn't slice
참조 의미 및 복사 방지 . 비 다형성 유형의 경우 포인터(또는 참조)는 잠재적으로 비용이 많이 드는 객체 복사를 방지합니다.
Base b; fun(b); // copies b, potentially expensive gun(&b); // takes a pointer to b, no copying hun(b); // regular syntax, behaves as a pointer
C++11에는 값 비싼 개체의 많은 복사본을 함수 인수와 반환 값으로 사용하지 않도록 할 수 있는 이동 의미 체계가 있습니다. 그러나 포인터를 사용하면 확실히 그런 것을 피할 수 있고 동일한 객체에 여러 포인터를 사용할 수 있습니다(객체는 한 번만 이동할 수 있음).
자원 획득 . new
연산자를 사용하여 리소스에 대한 포인터를 만드는 것은 현대 C++에서 안티 패턴입니다. 특수 리소스 클래스(표준 컨테이너 중 하나) 또는 스마트 포인터 ( std::unique_ptr<>
또는 std::shared_ptr<>
)를 사용하십시오. 고려하다:
{ auto b = new Base; ... // oops, if an exception is thrown, destructor not called! delete b; }
대
{ auto b = std::make_unique<Base>(); ... // OK, now exception safe }
원시 포인터는 "보기"로만 사용해야 하며 직접 생성을 통하거나 반환 값을 통해 암시적으로 소유권과 관련해서는 안 됩니다. C++ FAQ에서 이 Q&A 도 참조하십시오.
보다 세분화된 수명 제어 공유 포인터가 복사될 때마다(예: 함수 인수로) 포인터가 가리키는 리소스는 활성 상태로 유지됩니다. 사용자가 직접 또는 리소스 클래스 내부에서 new
에 의해 생성되지 않은 일반 객체는 범위를 벗어날 때 소멸됩니다.
TemplateRex정방향 선언, 다형성 등의 중요한 사용 사례를 포함하여 이 질문에 대한 훌륭한 답변이 많이 있습니다. 그러나 귀하의 질문에 "영혼"의 일부가 답변되지 않은 것 같습니다. 즉, Java 및 C++에서 다양한 구문이 의미하는 바는 무엇입니까?
두 언어를 비교하는 상황을 살펴보겠습니다.
자바:
Object object1 = new Object(); //A new object is allocated by Java Object object2 = new Object(); //Another new object is allocated by Java object1 = object2; //object1 now points to the object originally allocated for object2 //The object originally allocated for object1 is now "dead" - nothing points to it, so it //will be reclaimed by the Garbage Collector. //If either object1 or object2 is changed, the change will be reflected to the other
이에 가장 가까운 것은 다음과 같습니다.
C++:
Object * object1 = new Object(); //A new object is allocated on the heap Object * object2 = new Object(); //Another new object is allocated on the heap delete object1; //Since C++ does not have a garbage collector, if we don't do that, the next line would //cause a "memory leak", ie a piece of claimed memory that the app cannot use //and that we have no way to reclaim... object1 = object2; //Same as Java, object1 points to object2.
대체 C++ 방식을 살펴보겠습니다.
Object object1; //A new object is allocated on the STACK Object object2; //Another new object is allocated on the STACK object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1, //using the "copy assignment operator", the definition of operator =. //But, the two objects are still different. Change one, the other remains unchanged. //Also, the objects get automatically destroyed once the function returns...
그것을 생각하는 가장 좋은 방법은 -- 다소간 -- Java는 (암시적으로) 객체에 대한 포인터를 처리하는 반면 C++는 객체에 대한 포인터나 객체 자체를 처리할 수 있다는 것입니다. 여기에는 예외가 있습니다. 예를 들어 Java "기본" 유형을 선언하면 포인터가 아니라 복사되는 실제 값입니다. 그래서,
자바:
int object1; //An integer is allocated on the stack. int object2; //Another integer is allocated on the stack. object1 = object2; //The value of object2 is copied to object1.
즉, 포인터를 사용하는 것이 반드시 사물을 처리하는 올바른 방법이나 잘못된 방법은 아닙니다. 그러나 다른 답변은 만족스럽게 다루었습니다. 그러나 일반적인 아이디어는 C++에서 개체의 수명과 개체가 위치할 위치를 훨씬 더 많이 제어할 수 있다는 것입니다.
참고로 Object * object = new Object()
구성은 실제로 일반적인 Java(또는 C#) 의미 체계에 가장 가깝습니다.
Gerasimos R머리말
Java는 과대 광고와 달리 C++와 다릅니다. Java 과대 광고 기계는 Java가 C++와 유사한 구문을 가지고 있기 때문에 언어가 유사하다고 믿길 바랍니다. 그 무엇도 진실에서 멀어질 수 없습니다. 이 잘못된 정보는 Java 프로그래머가 코드의 의미를 이해하지 못한 채 C++로 가서 Java와 유사한 구문을 사용하는 이유의 일부입니다.
앞으로 우리는 간다
하지만 우리가 왜 이런 식으로 해야 하는지 이해할 수 없습니다. 메모리 주소에 직접 액세스할 수 있기 때문에 효율성과 속도와 관련이 있다고 가정합니다. 내가 맞아?
반대로, 실제로. 스택은 힙에 비해 매우 간단하기 때문에 힙은 스택보다 훨씬 느립니다. 자동 저장 변수(일명 스택 변수)는 범위를 벗어나면 소멸자가 호출됩니다. 예를 들어:
{ std::string s; } // s is destroyed here
반면에 동적으로 할당된 포인터를 사용하는 경우 해당 소멸자를 수동으로 호출해야 합니다. delete
는 이 소멸자를 호출합니다.
{ std::string* s = new std::string; } delete s; // destructor called
이것은 C# 및 Java에서 널리 new
구문과 관련이 없습니다. 그들은 완전히 다른 목적으로 사용됩니다.
동적 할당의 이점
1. 배열의 크기를 미리 알 필요가 없다
많은 C++ 프로그래머가 겪는 첫 번째 문제 중 하나는 사용자로부터 임의의 입력을 수락할 때 스택 변수에 고정 크기만 할당할 수 있다는 것입니다. 배열의 크기도 변경할 수 없습니다. 예를 들어:
char buffer[100]; std::cin >> buffer; // bad input = buffer overflow
std::string
대신 사용했다면 std::string
내부적으로 자체 크기를 조정하므로 문제가 되지 않습니다. 그러나 본질적으로 이 문제에 대한 해결책은 동적 할당입니다. 사용자 입력에 따라 동적 메모리를 할당할 수 있습니다. 예를 들면 다음과 같습니다.
int * pointer; std::cout << "How many items do you need?"; std::cin >> n; pointer = new int[n];
참고 : 많은 초보자가 저지르는 실수 중 하나는 가변 길이 배열을 사용하는 것입니다. 이것은 많은 GCC 확장을 미러링하기 때문에 GNU 확장이자 Clang의 하나이기도 합니다. 따라서 다음 int arr[n]
의존해서는 안 됩니다.
힙이 스택보다 훨씬 크기 때문에 필요한 만큼의 메모리를 임의로 할당/재할당할 수 있지만 스택에는 한계가 있습니다.
2. 배열은 포인터가 아닙니다.
이것이 어떻게 당신이 요구하는 이점입니까? 배열과 포인터 뒤에 있는 혼란/신화를 이해하면 답이 명확해질 것입니다. 일반적으로 동일하다고 가정하지만 그렇지 않습니다. 이 미신은 포인터가 배열처럼 첨자될 수 있다는 사실과 배열 때문에 함수 선언에서 최상위 수준의 포인터로 소멸된다는 사실에서 비롯됩니다. 배열이 포인터로 붕괴 그러나, 일단 포인터는 잃는다 sizeof
정보를 표시합니다. 따라서 sizeof(pointer)
는 포인터의 크기를 바이트 단위로 제공하며, 이는 일반적으로 64비트 시스템에서 8바이트입니다.
배열에 할당할 수 없으며 초기화만 할 수 있습니다. 예를 들어:
int arr[5] = {1, 2, 3, 4, 5}; // initialization int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array // be given by the amount of members in the initializer arr = { 1, 2, 3, 4, 5 }; // ERROR
반면 포인터로 원하는 모든 작업을 수행할 수 있습니다. 불행히도 포인터와 배열의 구분은 Java와 C#에서 수동적이기 때문에 초보자는 차이점을 이해하지 못합니다.
3. 다형성
as
키워드를 사용하여 개체를 다른 개체로 취급할 수 있는 기능이 있습니다. 따라서 누군가가 Entity
객체를 Player
객체 Player player = Entity as Player;
이것은 특정 유형에만 적용되어야 하는 동종 컨테이너에서 함수를 호출하려는 경우에 매우 유용합니다. 기능은 아래와 유사한 방식으로 달성할 수 있습니다.
std::vector<Base*> vector; vector.push_back(&square); vector.push_back(&triangle); for (auto& e : vector) { auto test = dynamic_cast<Triangle*>(e); // I only care about triangles if (!test) // not a triangle e.GenericFunction(); else e.TriangleOnlyMagic(); }
따라서 Triangles에만 회전 함수가 있는 경우 클래스의 모든 개체에서 호출하려고 하면 컴파일러 오류가 발생합니다. dynamic_cast
사용 as
키워드를 시뮬레이션할 수 있습니다. 분명히 하기 위해 캐스트가 실패하면 잘못된 포인터를 반환합니다. 따라서 !test
test
가 NULL인지 또는 유효하지 않은 포인터인지 확인하기 위한 축약형이며, 이는 캐스트가 실패했음을 의미합니다.
자동 변수의 이점
동적 할당이 할 수 있는 모든 훌륭한 일을 본 후에 왜 아무도 항상 동적 할당을 사용하지 않는지 궁금할 것입니다. 내가 이미 한 가지 이유를 말했는데, 힙이 느립니다. 그리고 그 모든 메모리가 필요하지 않다면 남용해서는 안 됩니다. 특별한 순서 없이 다음과 같은 몇 가지 단점이 있습니다.
오류가 발생하기 쉽습니다. 수동 메모리 할당은 위험하고 누수가 발생하기 쉽습니다. valgrind
(메모리 누수 도구) 사용에 능숙하지 않다면 머리가 멍해질 수 있습니다. 운 좋게도 RAII 관용구와 스마트 포인터가 이를 약간 완화하지만 Rule Of Three 및 Rule Of Five와 같은 관행에 익숙해야 합니다. 그것은 많은 정보를 취해야 하며, 모르거나 관심이 없는 초보자는 이 함정에 빠질 것입니다.
필요하지 않습니다. new
키워드를 사용하는 것이 관용적인 Java 및 C#과 달리 C++에서는 필요한 경우에만 사용해야 합니다. 일반적인 문구는 망치가 있으면 모든 것이 못처럼 보입니다. C++로 시작하는 초보자는 포인터를 두려워하고 습관적으로 스택 변수를 사용하는 법을 배우는 반면 Java 및 C# 프로그래머는 포인터를 이해하지 않고 포인터를 사용하여 시작합니다! 그것은 말 그대로 잘못된 발을 내딛는 것입니다. 구문이 하나이고 언어를 배우는 것이 또 다른 것이기 때문에 알고 있는 모든 것을 버려야 합니다.
1. (N)RVO - 일명, (명명) 반환 값 최적화
많은 컴파일러가 수행하는 최적화 중 하나는 elision 및 반환 값 최적화 라고 하는 것입니다. 이러한 것들은 많은 요소를 포함하는 벡터와 같이 매우 큰 객체에 유용한 불필요한 복사본을 방지할 수 있습니다. 일반적으로 일반적인 관행은 큰 개체를 복사하여 이동 하는 대신 포인터를 사용하여 소유권 을 이전하는 것입니다. 이것은 이동 의미론 과 스마트 포인터 의 시작으로 이어졌습니다.
포인터를 사용하는 경우 (N)RVO가 발생 하지 않습니다. 최적화가 걱정된다면 포인터를 반환하거나 전달하는 것보다 (N)RVO를 활용하는 것이 더 유익하고 오류가 덜 발생합니다. 함수 호출자가 동적으로 할당된 개체 등 delete
하는 책임이 있는 경우 오류 누출이 발생할 수 있습니다. 포인터가 뜨거운 감자처럼 전달되는 경우 개체의 소유권을 추적하기 어려울 수 있습니다. 스택 변수가 더 간단하고 좋기 때문에 그냥 사용하십시오.
user3391320포인터를 사용하는 또 다른 좋은 이유는 정방향 선언 입니다. 충분히 큰 프로젝트에서는 실제로 컴파일 시간을 단축할 수 있습니다.
Burnt ToastC++에서 스택에 할당된 개체( Object object;
사용, 블록 내 명령문)는 선언된 범위 내에서만 유지됩니다. 코드 블록 실행이 완료되면 선언된 개체가 소멸됩니다. Object* obj = new Object()
사용하여 힙에 메모리를 할당 delete obj
를 호출할 때까지 계속 힙에 있습니다.
선언/할당된 코드 블록에서 뿐만 아니라 개체를 사용하고 싶을 때 힙에 개체를 생성합니다.
Karthik KalyanasundaramC++에서는 포인터, 참조 및 값으로 개체를 전달하는 세 가지 방법을 제공합니다. Java는 후자로 제한합니다(유일한 예외는 int, boolean 등과 같은 기본 유형입니다). 이상한 장난감처럼 C++를 사용하지 않으려면 이 세 가지 방법의 차이점을 아는 것이 좋습니다.
Java는 '누가 언제 이것을 파괴해야 하는가?'와 같은 문제가 없는 척합니다. 정답은 Garbage Collector, Great and Awful입니다. 그럼에도 불구하고 메모리 누수에 대해 100% 보호를 제공할 수는 없습니다(예, java 는 메모리 누수가 발생할 수 있음). 사실 GC는 잘못된 안전감을 줍니다. SUV가 클수록 대피소까지 가는 길이 더 길어집니다.
C++는 객체의 수명 주기 관리와 직접 대면합니다. 글쎄, 그것을 처리하는 수단이 있지만( 스마트 포인터 제품군, Qt의 QObject 등), 그 중 어느 것도 GC와 같은 '발화 후 잊어버리기' 방식으로 사용할 수 없습니다. 항상 메모리 처리를 염두에 두어야 합니다. 개체를 파괴하는 데 신경을 써야 할 뿐만 아니라 동일한 개체를 두 번 이상 파괴하지 않도록 해야 합니다.
아직 두렵지 않으세요? Ok: 순환 참조 - 직접 처리하십시오. 그리고 기억하십시오. 각 개체를 정확히 한 번만 죽이십시오. 우리 C++ 런타임은 시체를 어지럽히는 사람을 좋아하지 않으며 죽은 사람은 그대로 두십시오.
질문으로 돌아갑니다.
포인터나 참조가 아닌 값으로 개체를 전달할 때 개체(전체 개체, 몇 바이트이든 거대한 데이터베이스 덤프이든 상관없이 - 후자를 피하도록 주의할 만큼 똑똑합니다. t you?) '='를 할 때마다. 그리고 개체의 구성원에 액세스하려면 '.'를 사용합니다. (점).
포인터로 개체를 전달할 때 몇 바이트(32비트 시스템에서는 4바이트, 64비트 시스템에서는 8바이트), 즉 이 개체의 주소만 복사합니다. 그리고 이것을 모든 사람에게 보여주기 위해 멤버에 액세스할 때 이 멋진 '->' 연산자를 사용합니다. 또는 '*'와 '.'의 조합을 사용할 수 있습니다.
참조를 사용하면 값인 척하는 포인터를 얻습니다. 포인터이지만 '.'를 통해 멤버에 액세스합니다.
그리고 한 번 더 생각나게 하려면: 여러 변수를 쉼표로 구분하여 선언할 때 (손을 조심하세요):
- 유형은 모든 사람에게 제공됩니다.
- 값/포인터/참조 수정자는 개별적입니다.
예시:
struct MyStruct { int* someIntPointer, someInt; //here comes the surprise MyStruct *somePointer; MyStruct &someReference; }; MyStruct s1; //we allocated an object on stack, not in heap s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual s1.someIntPointer = &s1.someInt; *s1.someIntPointer = 2; //now s1.someInt has value '2' s1.somePointer = &s1; s1.someReference = s1; //note there is no '&' operator: reference tries to look like value s1.somePointer->someInt = 3; //now s1.someInt has value '3' *(s1.somePointer).someInt = 3; //same as above line *s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4' s1.someReference.someInt = 5; //now s1.someInt has value '5' //although someReference is not value, it's members are accessed through '.' MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back. //OK, assume we have '=' defined in MyStruct s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one
Kirill Gamazkov하지만 왜 우리가 이것을 이렇게 사용해야 하는지 이해할 수 없습니다.
다음을 사용하는 경우 함수 본문 내에서 작동하는 방식을 비교할 것입니다.
Object myObject;
함수 내에서 이 함수가 반환되면 myObject
따라서 함수 외부에 개체가 필요하지 않은 경우에 유용합니다. 이 개체는 현재 스레드 스택에 배치됩니다.
함수 본문 내부에 작성하는 경우:
Object *myObject = new Object;
myObject
가 가리키는 Object 클래스 인스턴스는 함수가 종료되고 할당이 힙에 있으면 소멸되지 않습니다.
이제 Java 프로그래머라면 두 번째 예제는 Java에서 객체 할당이 작동하는 방식에 더 가깝습니다. 이 줄: Object *myObject = new Object;
Java와 동일합니다. Object myObject = new Object();
. 차이점은 자바에서 myObject는 가비지 수집을 하는 반면 C++에서는 해제되지 않는다는 것입니다. 어딘가에서 명시적으로 `delete myObject;'를 호출해야 합니다. 그렇지 않으면 메모리 누수가 발생합니다.
C++11 이후로 값을 shared_ptr/unique_ptr에 저장하여 안전한 동적 할당 방법인 new Object
std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared"); // since c++14 std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared");
또한 객체는 맵 또는 벡터와 같은 컨테이너에 매우 자주 저장되며 객체의 수명을 자동으로 관리합니다.
marcinj기술적으로 이것은 메모리 할당 문제이지만 여기에 두 가지 더 실용적인 측면이 있습니다. 1) 범위, 포인터 없이 객체를 정의하면 객체가 정의된 코드 블록 이후에는 더 이상 객체에 액세스할 수 없습니다. 반면에 "new"로 포인터를 정의하면 동일한 포인터에서 "delete"를 호출할 때까지 이 메모리에 대한 포인터가 있는 곳이면 어디에서나 액세스할 수 있습니다. 2) 함수에 인수를 전달하려면 더 효율적으로 포인터나 참조를 전달해야 합니다. 객체를 전달하면 객체가 복사됩니다. 이것이 많은 메모리를 사용하는 객체인 경우 CPU를 소모할 수 있습니다(예: 데이터로 가득 찬 벡터 복사). 포인터를 전달할 때 전달하는 모든 것은 하나의 int입니다(구현에 따라 다르지만 대부분은 하나의 int입니다).
그 외에 "new"는 특정 시점에서 해제해야 하는 힙에 메모리를 할당한다는 것을 이해해야 합니다. "new"를 사용할 필요가 없는 경우 "스택에서" 일반 객체 정의를 사용하는 것이 좋습니다.
in need of help하자가 당신이 말할 class A
가 포함 class B
당신이 어떤 함수를 호출 할 때 class B
외부의 class A
단순히이 클래스에 대한 포인터를 얻을 것이다 당신은 당신이 원하는 무엇이든 할 수 있고 또한의 문맥 변경됩니다 class B
에 당신의 class A
그러나 동적 개체에 주의하십시오.
Quest객체에 대한 포인터를 사용하면 많은 이점이 있습니다.
- 효율성(이미 지적한 대로). 객체를 함수에 전달한다는 것은 객체의 새 복사본을 만드는 것을 의미합니다.
- 타사 라이브러리의 개체 작업. 개체가 타사 코드에 속해 있고 작성자가 포인터를 통해서만(복사 생성자가 아닌) 개체를 사용하려는 경우 이 개체를 전달할 수 있는 유일한 방법은 포인터를 사용하는 것입니다. 값으로 전달하면 문제가 발생할 수 있습니다. (딥 카피/얕은 카피 문제).
- 개체가 리소스를 소유하고 소유권이 다른 개체와 공유되지 않도록 하려는 경우.
Rohit주요 질문은 객체 자체가 아닌 포인터를 사용해야 하는 이유입니다. 그리고 내 대답은 (거의) 개체 대신 포인터를 사용해서는 안 됩니다. C++에는 참조 가 있기 때문에 포인터보다 안전하고 포인터와 동일한 성능을 보장하기 때문입니다.
귀하의 질문에서 언급한 또 다른 사항:
Object *myObject = new Object;
어떻게 작동합니까? Object
유형의 포인터를 생성하고 하나의 객체에 맞게 메모리를 할당하고 기본 생성자를 호출합니다. 좋은 것 같죠? 그러나 실제로는 그렇게 좋지 않습니다. 메모리를 동적으로 할당한 경우( used 키워드 new
) 수동으로 메모리를 해제해야 합니다. 즉, 코드에서 다음이 있어야 합니다.
delete myObject;
이것은 소멸자를 호출하고 메모리를 해제하고 쉬워 보이지만 큰 프로젝트에서는 한 스레드가 메모리를 해제했는지 여부를 감지하기 어려울 수 있지만 이를 위해 공유 포인터를 시도할 수 있습니다. 성능이 약간 저하되지만 작업하기가 훨씬 쉽습니다. 그들을.
이제 몇 가지 소개가 끝났고 질문으로 돌아갑니다.
함수 간에 데이터를 전송하는 동안 더 나은 성능을 얻기 위해 개체 대신 포인터를 사용할 수 있습니다.
보세요, 당신은 std::string
(그것도 객체입니다)을 가지고 있고 그것은 예를 들어 큰 XML과 같은 정말 많은 데이터를 포함하고 있습니다. 이제 그것을 구문 분석해야 하지만 이를 위해 void foo(...)
함수가 있습니다. 다음과 같이 다양한 방식으로 선언됩니다.
-
void foo(std::string xml);
이 경우 변수의 모든 데이터를 함수 스택으로 복사하고 시간이 걸리므로 성능이 저하됩니다. -
void foo(std::string* xml);
size_t
변수를 전달하는 것과 동일한 속도로 객체에 대한 포인터를 전달 NULL
포인터 또는 잘못된 포인터를 전달할 수 있기 때문에 오류가 발생하기 쉽습니다. 참조가 없기 때문에 C
에서 사용되는 포인터. -
void foo(std::string& xml);
여기서 참조를 전달합니다. 기본적으로 포인터를 전달하는 것과 동일하지만 컴파일러가 몇 가지 작업을 수행하고 잘못된 참조를 전달할 수 없습니다(실제로 잘못된 참조가 있는 상황을 생성할 수 있지만 컴파일러를 속이는 것입니다). -
void foo(const std::string* xml);
이것은 두 번째와 동일하며 포인터 값만 변경할 수 없습니다. -
void foo(const std::string& xml);
이것은 세 번째와 동일하지만 객체 값은 변경할 수 없습니다.
new
또는 regular 사용 )에 상관없이 이 5가지 방법을 사용하여 데이터를 전달할 수 있습니다.
언급할 또 다른 사항은 일반적인 방식으로 객체를 생성할 때 메모리를 스택에 할당하지만 new
생성하는 동안 힙을 할당한다는 것입니다. 스택을 할당하는 것이 훨씬 빠르지만 실제로 큰 데이터 배열에 대해서는 약간 작기 때문에 큰 개체가 필요한 경우 스택 오버플로가 발생할 수 있으므로 힙을 사용해야 하지만 일반적으로 이 문제는 STL 컨테이너를 사용하여 해결되며 기억하십시오. std::string
은 컨테이너이기도 합니다. 일부 사람들은 잊어버렸습니다. :)
ST3이것은 길게 논의되었지만 Java에서는 모든 것이 포인터입니다. 스택 할당과 힙 할당(모든 개체가 힙에 할당됨)을 구분하지 않으므로 포인터를 사용하고 있다는 사실을 인식하지 못합니다. C++에서는 메모리 요구 사항에 따라 둘을 혼합할 수 있습니다. 성능과 메모리 사용량은 C++에서 더 결정적입니다.
cmollisObject *myObject = new Object;
이렇게 하면 메모리 누수 를 방지하기 위해 명시적으로 삭제되어야 하는 객체(힙에 있는)에 대한 참조가 생성됩니다.
Object myObject;
이렇게 하면 객체(myObject)가 범위를 벗어날 때 자동으로 삭제되는 자동 유형(스택에)의 객체(myObject)가 생성됩니다.
Palak Jain포인터는 개체의 메모리 위치를 직접 참조합니다. Java에는 이와 같은 것이 없습니다. Java에는 해시 테이블을 통해 객체의 위치를 참조하는 참조가 있습니다. 이러한 참조를 사용하여 Java에서 포인터 산술과 같은 작업을 수행할 수 없습니다.
귀하의 질문에 대한 답변은 귀하의 선호 사항일 뿐입니다. Java와 유사한 구문을 사용하는 것을 선호합니다.
Cozmolasan포인터를 사용하는 한 가지 이유는 C 함수와 인터페이스하는 것입니다. 또 다른 이유는 메모리를 절약하는 것입니다. 예를 들어: 많은 데이터를 포함하고 프로세서 집약적인 복사 생성자가 있는 객체를 함수에 전달하는 대신 객체에 대한 포인터를 전달하면 특히 루프에 있는 경우 메모리와 속도를 절약할 수 있습니다. C 스타일 배열을 사용하지 않는 한 이 경우 참조가 더 좋습니다.
user6244076메모리 사용률이 가장 높은 영역에서는 포인터가 유용합니다. 예를 들어, 재귀 루틴을 사용하여 수천 개의 노드가 생성되고 나중에 이를 사용하여 게임에서 차선책을 평가하는 미니맥스 알고리즘을 생각해 보십시오. 할당 해제 또는 재설정(스마트 포인터에서와 같이) 기능은 메모리 소비를 크게 줄입니다. 비포인터 변수는 재귀 호출이 값을 반환할 때까지 계속 공간을 차지합니다.
seccpur포인터의 중요한 사용 사례를 하나 포함하겠습니다. 기본 클래스에 일부 객체를 저장할 때 다형성일 수 있습니다.
Class Base1 { }; Class Derived1 : public Base1 { }; Class Base2 { Base *bObj; virtual void createMemerObects() = 0; }; Class Derived2 { virtual void createMemerObects() { bObj = new Derived1(); } };
따라서 이 경우 bObj를 직접 객체로 선언할 수 없으며 포인터가 있어야 합니다.
user18853C++에서 객체 포인터의 핵심 강점은 동일한 슈퍼클래스의 포인터 맵과 다형성 배열을 허용한다는 것입니다. 예를 들어 잉꼬, 닭, 로빈, 타조 등을 Bird 배열에 넣을 수 있습니다.
또한 동적으로 할당된 개체는 더 유연하고 HEAP 메모리를 사용할 수 있는 반면 로컬로 할당된 개체는 정적이 아닌 경우 STACK 메모리를 사용합니다. 특히 재귀를 사용할 때 스택에 큰 개체가 있으면 의심할 여지 없이 스택 오버플로가 발생합니다.
RollerSimmer"필요는 발명의 어머니다." 내가 지적하고 싶은 가장 중요한 차이점은 코딩 경험의 결과입니다. 때로는 객체를 함수에 전달해야 합니다. 이 경우 객체가 매우 큰 클래스인 경우 객체로 전달하면 상태가 복사되므로(원하지 않을 수도 있음.. AND CAN BE BIG OVERHEAD) 포인터가 고정되어 있는 동안 객체를 복사하는 오버헤드가 발생합니다. 4바이트 크기(32비트로 가정). 다른 이유는 이미 위에서 언급했습니다 ...
sandeep bisht이미 훌륭한 답변이 많이 있지만 한 가지 예를 들어보겠습니다.
간단한 Item 클래스가 있습니다.
class Item { public: std::string name; int weight; int price; };
나는 그것들을 묶을 벡터를 만듭니다.
std::vector<Item> inventory;
100만 개의 Item 개체를 만들고 벡터에 다시 푸시합니다. 이름별로 벡터를 정렬한 다음 특정 항목 이름에 대해 간단한 반복 이진 검색을 수행합니다. 프로그램을 테스트하고 실행을 완료하는 데 8분 이상 걸립니다. 그런 다음 인벤토리 벡터를 다음과 같이 변경합니다.
std::vector<Item *> inventory;
...그리고 new를 통해 백만 개의 Item 객체를 생성합니다. 내 코드에 대한 유일한 변경 사항은 마지막에 메모리 정리를 위해 추가하는 루프를 제외하고 항목에 대한 포인터를 사용하는 것입니다. 이 프로그램은 40초 이내에 실행되거나 속도가 10배 이상 빨라집니다. 편집: 코드는 http://pastebin.com/DK24SPeW에 있습니다 . 컴파일러 최적화를 사용하면 방금 테스트한 컴퓨터에서 3.4배만 증가했으며 이는 여전히 상당한 수준입니다.
Darren출처 : http:www.stackoverflow.com/questions/22146094/why-should-i-use-a-pointer-rather-than-the-object-itself