development

참조 대 포인터를 사용하는 경우

big-blog 2020. 2. 28. 19:21
반응형

참조 대 포인터를 사용하는 경우


포인터와 참조의 구문과 일반적인 의미를 이해하지만 API에서 참조 또는 포인터를 사용하는 것이 적절한 지 어떻게 결정해야합니까?

당연히 일부 상황에는 하나 또는 다른 상황이 필요 operator++하지만 (참조 인수가 필요) 일반적으로 변수가 파괴적으로 전달된다는 구문이 명확하기 때문에 포인터 (및 const 포인터)를 사용하는 것을 선호합니다.

예를 들어 다음 코드에서 :

void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
  int a = 0;
  add_one(a); // Not clear that a may be modified
  add_one(&a); // 'a' is clearly being passed destructively
}

포인터를 사용하면 항상 무슨 일이 벌어지고 있는지 알기 때문에 명확성이 중요한 API 등의 경우 참조보다 포인터가 적절하지 않은 포인터입니까? 이것이 참조가 필요할 때만 사용해야한다는 것을 의미합니까 (예 :) operator++? 하나 또는 다른 성능 문제가 있습니까?

수정 된 날짜 :

NULL 값을 허용하고 원시 배열을 처리하는 것 외에도 선택은 개인 취향에 달려 있습니다. Google의 C ++ 스타일 가이드 를 참조하는 아래의 답변을 수락 했습니다. "참조는 값 구문이 있지만 포인터 의미가 있기 때문에 혼동 될 수 있습니다"라는 견해를 제시합니다.

NULL이 아니어야하는 포인터 인수를 삭제하는 데 추가 작업이 필요하기 때문에 (예 : add_one(0)포인터 버전을 호출하고 런타임 중에 중단됨) 유지 보수성 관점에서 개체가 반드시 있어야하는 경우 참조를 사용하는 것이 합리적입니다. 구문의 명확성을 잃습니다.


가능한 곳 어디에서나 참조를 사용하십시오.

할 수 없을 때까지 포인터를 피하십시오.

그 이유는 포인터가 다른 구성 요소보다 읽기 / 읽기가 더 어렵고 덜 안전하며 훨씬 더 위험한 조작을하기 때문입니다.

따라서 경험상 다른 선택이없는 경우에만 포인터를 사용하는 것이 좋습니다.

예를 들어, 함수가 nullptr을 반환 할 수있는 경우 객체에 대한 포인터를 반환하는 것이 유효한 옵션이며, 예상되는 경우입니다. 즉, 더 나은 옵션은와 비슷한 것을 사용하는 것 boost::optional입니다.

또 다른 예는 특정 메모리 조작을 위해 원시 메모리에 대한 포인터를 사용하는 것입니다. 전체 코드베이스의 위험한 부분을 제한하기 위해 코드의 매우 좁은 부분에 숨겨져 현지화되어야합니다.

귀하의 예에서는 다음과 같은 이유로 포인터를 인수로 사용하는 것이 중요하지 않습니다.

  1. nullptr당신이 논쟁의 여지가 있다면 , 당신은 정의되지 않은 행동의 땅으로 갈 것입니다;
  2. 참조 속성 버전은 (1을 사용하여 트릭을 쉽게 찾을 수 없음) 문제를 허용하지 않습니다.
  3. 참조 속성 버전은 사용자가 이해하기 더 간단합니다. null이 될 수있는 것이 아니라 유효한 객체를 제공해야합니다.

함수의 동작이 주어진 객체의 유무에 관계없이 작동 해야하는 경우 포인터를 속성 nullptr으로 사용하면 인수로 전달할 수 있으며 함수에 적합하다는 것을 나타냅니다. 그것은 사용자와 구현 사이의 일종의 계약입니다.


참조는 내부적으로 포인터로 구현되므로 성능은 동일합니다. 따라서 걱정할 필요가 없습니다.

참조 및 포인터 사용시기와 관련하여 일반적으로 허용되는 규칙은 없습니다. 경우에 따라 참조 (예 : 복사 생성자)를 반환하거나 수락해야하지만 원하는 경우를 제외하고 자유롭게 할 수 있습니다. 필자가 만난 일반적인 규칙은 매개 변수가 기존 객체를 참조해야 할 때 참조를 사용하고 NULL 값이 괜찮을 때 포인터를 사용하는 것입니다.

Google 과 같은 일부 코딩 규칙 은 참조가 구문이 명확하지 않기 때문에 항상 포인터 또는 const 참조를 사용해야한다고 규정합니다.


에서 C ++ FAQ 라이트 -

가능하면 참조를 사용하고 필요할 때는 포인터를 사용하십시오.

참조는 "재 부착"이 필요하지 않을 때마다 포인터보다 선호됩니다. 이것은 일반적으로 참조가 클래스의 공용 인터페이스에서 가장 유용하다는 것을 의미합니다. 참조는 일반적으로 객체의 스킨에 나타나고 내부에 포인터가 나타납니다.

위의 예외는 함수의 매개 변수 또는 반환 값에 "센티넬"참조 (객체를 참조하지 않는 참조)가 필요한 경우입니다. 이것은 일반적으로 포인터를 반환 / 가져 와서 NULL 포인터 에이 특별한 의미를 부여하는 것이 가장 좋습니다 (참조는 항상 역 참조 된 NULL 포인터가 아닌 객체의 별칭을 지정해야 함).

참고 : 구식 C 프로그래머는 호출자의 코드에 명시되지 않은 참조 시맨틱을 제공하기 때문에 때때로 참조를 좋아하지 않습니다. 그러나 일부 C ++ 경험을 한 후에는 이것이 정보 숨기기의 형태라는 것을 빨리 인식하고, 이는 책임이 아닌 자산입니다. 예를 들어, 프로그래머는 기계의 언어가 아니라 문제의 언어로 코드를 작성해야합니다.


내 경험 법칙은 다음과 같습니다.

  • 나가거나 들어가거나 나가는 매개 변수에 포인터를 사용하십시오. 따라서 값이 변경 될 것임을 알 수 있습니다. (을 사용해야합니다 &)
  • NULL 매개 변수가 허용 가능한 값이면 포인터를 사용하십시오. ( const수신 매개 변수 인지 확인하십시오 )
  • 들어오는 매개 변수가 NULL 일 수없고 기본 유형 ( const T&) 이 아닌 경우 수신 매개 변수에 대한 참조를 사용하십시오 .
  • 새로 만든 객체를 반환 할 때 포인터 또는 스마트 포인터를 사용하십시오.
  • 포인터 대신 포인터 나 스마트 포인터를 구조체 나 클래스 멤버로 사용하십시오.
  • 앨리어싱 사용 참조 (예. int &current = someArray[i])

어떤 것을 사용하든, 명확하지 않은 경우 함수와 매개 변수의 의미를 문서화하는 것을 잊지 마십시오.


다른 사람들이 이미 대답했듯이 : NULL/ 변수 nullptr실제로 유효한 상태 아닌 한 항상 참조를 사용하십시오 .

이 주제에 대한 John Carmack의 견해는 비슷합니다.

NULL 포인터는 적어도 코드에서 C / C ++에서 가장 큰 문제입니다. 단일 값을 플래그와 주소로 이중 사용하면 치명적인 문제가 발생합니다. C ++ 참조는 가능할 때마다 포인터보다 선호되어야합니다. 참조는 "진정한"포인터 일뿐, NULL이 아닌 암시 적 계약을 갖습니다. 포인터가 참조로 바뀔 때 NULL 검사를 수행하면 그 이후의 문제를 무시할 수 있습니다.

http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

2012-03-13 수정

Bret Kuhns 사용자 는 다음과 같이 말합니다.

C ++ 11 표준이 완성되었습니다. 이 스레드에서 대부분의 코드가 참조, shared_ptr 및 unique_ptr의 조합으로 완벽하게 잘 수행되어야한다고 언급 할 때가되었습니다.

충분히 사실이지만, 원시 포인터를 스마트 포인터로 교체하더라도 문제는 여전히 남아 있습니다.

예를 들어, 모두 std::unique_ptrstd::shared_ptr기본 생성자를 통해 "빈"포인터로 구성 할 수있다 :

... 비어 있지 않은지 확인하지 않고 사용하면 충돌이 발생할 위험이 있습니다. 이것은 J. Carmack의 토론과 관련이 있습니다.

그리고 "우리는 어떻게 스마트 포인터를 함수 매개 변수로 전달합니까?"라는 재미있는 문제가 있습니다.

질문 C ++에 대한 Jon답변 -boost :: shared_ptr에 대한 참조 전달 다음 의견은 스마트 포인터를 복사 또는 참조로 전달하는 것이 원하는만큼 명확하지 않다는 것을 보여줍니다 ( by-reference "로 기본 설정되어 있지만 잘못되었을 수 있습니다).


면책 조항 : 참조가 NULL 또는 "리바운드"가 될 수 없다는 사실 이외 (타이어는 별명 인 객체를 변경할 수 없음을 의미 함) 실제로 맛의 문제로 귀결됩니다. "이게 낫다".

즉, 게시물의 마지막 진술에 동의하지 않습니다. 코드가 참조로 명확성을 잃지 않는다고 생각합니다. 귀하의 예에서

add_one(&a);

보다 명확하다

add_one(a);

왜냐하면 아마도 a의 가치가 변할 것임을 알고 있기 때문입니다. 반면에 함수의 서명

void add_one(int* const n);

n은 단일 정수 또는 배열입니까? 때로는 (잘 문서화되지 않은) 헤더 및 다음과 같은 서명에만 액세스 할 수 있습니다

foo(int* const a, int b);

첫눈에 이해하기 쉽지 않습니다.

Imho, 참조는 (재 설명 된 의미에서) (재) 할당이나 리 바인딩이 필요하지 않을 때 포인터만큼 좋습니다. 또한 개발자가 배열에 대한 포인터 만 사용하는 경우 함수 서명이 다소 모호합니다. 연산자 구문이 참조로 더 읽기 쉽다는 사실은 말할 것도 없습니다.


맛의 문제가 아닙니다. 다음은 몇 가지 결정적인 규칙입니다.

선언 된 범위 내에서 정적으로 선언 된 변수를 참조하려면 C ++ 참조를 사용하면 완벽하게 안전합니다. 정적으로 선언 된 스마트 포인터에도 동일하게 적용됩니다. 매개 변수를 참조로 전달하는 것이이 사용법의 예입니다.

범위가 선언 된 범위보다 넓은 범위에서 무언가를 참조하려면 참조 카운트 스마트 포인터를 사용하여 완벽하게 안전해야합니다.

구문상의 편의를 위해 참조가있는 컬렉션 요소를 참조 할 수 있지만 안전하지는 않습니다. 언제든지 요소를 삭제할 수 있습니다.

컬렉션의 요소에 대한 참조를 안전하게 유지하려면 참조 횟수 스마트 포인터를 사용해야합니다.


성능 차이는 너무 작아서 덜 분명한 접근 방식을 사용하여 정당화 할 수는 없습니다.

첫째, 참고 문헌이 일반적으로 우수한 경우에 언급되지 않은 사례는 const참고 문헌입니다. 단순하지 않은 유형의 경우, a를 전달 const reference하면 임시 작성을 피할 수 있으며 값이 수정되지 않기 때문에 우려되는 혼란을 유발하지 않습니다. 여기에서 사람이 포인터를 전달하도록 강요하면 주소가 가져오고 함수에 전달되는 것을 볼 때 값이 변경되었다고 생각할 수 있으므로 걱정이 심합니다.

어쨌든, 나는 기본적으로 당신에 동의합니다. 나는 이것이 함수가하는 것이 확실하지 않을 때 값을 수정하기 위해 참조를 취하는 함수를 좋아하지 않습니다. 이 경우 포인터를 사용하는 것이 좋습니다.

복잡한 유형의 값을 반환해야 할 때 참조를 선호하는 경향이 있습니다. 예를 들면 다음과 같습니다.

bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative

여기서 함수 이름을 사용하면 배열로 정보를 다시 가져올 수 있습니다. 따라서 혼란이 없습니다.

참조의 주요 장점은 항상 유효한 값을 포함하고 포인터보다 깨끗하며 추가 구문 없이도 다형성을 지원한다는 것입니다. 이러한 이점 중 어느 것도 적용되지 않으면 포인터보다 참조를 선호 할 이유가 없습니다.


위키 에서 복사 -

그 결과 많은 구현에서 참조를 통해 자동 또는 정적 수명을 갖는 변수에서 작동하는 것은 구문 적으로 직접 액세스하는 것과 유사하지만 비용이 많이 드는 숨겨진 역 참조 작업을 포함 할 수 있습니다. 참조는 식별자의 간접적 인 수준을 모호하게하기 때문에 구문 상 논란의 여지가있는 C ++ 기능입니다. 즉, 포인터가 일반적으로 구문 상 두드러지는 C 코드와 달리, 큰 C ++ 코드 블록에서 액세스중인 객체가 로컬 또는 전역 변수로 정의되어 있는지 또는 참조 (암시 적 포인터)인지 여부는 즉시 명확하지 않을 수 있습니다. 다른 위치, 특히 코드가 참조와 포인터를 혼합하는 경우. 이 측면은 잘못 작성된 C ++ 코드를 읽고 디버깅하기 어렵게 만들 수 있습니다 (별칭 참조).

나는 이것에 100 % 동의하고, 이것이 당신이 그렇게 할만한 이유가있을 때만 참고를 사용해야한다고 믿는 이유입니다.


명심해야 할 사항 :

  1. 포인터는 NULL참조가 될 수 없습니다 NULL.

  2. 참조는 사용하기 쉽고 const값을 변경하고 싶지 않고 함수에서 참조가 필요할 때 참조에 사용할 수 있습니다.

  3. 와 함께 사용되는 *while 참조 와 함께 사용되는 포인터 &.

  4. 포인터 산술 연산이 필요한 경우 포인터를 사용하십시오.

  5. void 유형에 대한 포인터는 가질 수 int a=5; void *p = &a;있지만 void 유형에 대한 참조는 가질 수 없습니다.

포인터 대 참조

void fun(int *a)
{
    cout<<a<<'\n'; // address of a = 0x7fff79f83eac
    cout<<*a<<'\n'; // value at a = 5
    cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
    cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
    cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);

무엇을 사용해야하는지 평결

포인터 : 배열, 링크 목록, 트리 구현 및 포인터 산술.

참조 : 함수 매개 변수 및 리턴 유형


" 가능한 경우 참조 사용 "규칙에 문제점이 있으며 추가 사용을 위해 참조를 유지하려는 경우 발생합니다. 예를 들어 이것을 설명하기 위해 다음과 같은 수업이 있다고 상상해보십시오.

class SimCard
{
    public:
        explicit SimCard(int id):
            m_id(id)
        {
        }

        int getId() const
        {
            return m_id;
        }

    private:
        int m_id;
};

class RefPhone
{
    public:
        explicit RefPhone(const SimCard & card):
            m_card(card)
        {
        }

        int getSimId()
        {
            return m_card.getId();
        }

    private:
        const SimCard & m_card;
};

처음에는 RefPhone(const SimCard & card)생성자에 잘못된 / 널 포인터를 전달할 수 없으므로 생성자에 매개 변수를 참조로 전달 하는 것이 좋습니다 . 어떻게 든 스택에 변수를 할당하고 RAII의 이점을 얻습니다.

PtrPhone nullPhone(0);  //this will not happen that easily
SimCard * cardPtr = new SimCard(666);  //evil pointer
delete cardPtr;  //muahaha
PtrPhone uninitPhone(cardPtr);  //this will not happen that easily

그러나 임시 사람들은 당신의 행복한 세상을 파괴하기 위해옵니다.

RefPhone tempPhone(SimCard(666));   //evil temporary
//function referring to destroyed object
tempPhone.getSimId();    //this can happen

따라서 맹목적으로 참조를 고수하면 파괴 된 객체에 대한 참조를 저장할 가능성에 대해 유효하지 않은 포인터를 전달할 가능성과 상충되며 이는 기본적으로 동일한 효과입니다.

편집 : 규칙 "나는 당신이 할 수있는 곳마다 포인터를 사용하십시오. 당신이 할 수있을 때까지 포인터를 피하십시오." 가장 많이지지되고 승인 된 답변 (다른 답변도 제안)에서 명백해야하지만, 예는 그러한 참조가 나쁘다는 것을 보여주지 않습니다. 그러나 포인터처럼 오용 될 수 있으며 코드에 자체 위협을 가져올 수 있습니다.


포인터와 참조 사이에는 다음과 같은 차이점이 있습니다.

  1. 변수를 전달할 때 참조로 전달은 값으로 전달하는 것처럼 보이지만 포인터 의미가 있습니다 (포인터처럼 작동).
  2. 참조는 0으로 직접 초기화 할 수 없습니다 (널).
  3. 참조 (참조, 참조되지 않은 객체)는 수정할 수 없습니다 ( "* const"포인터와 동일).
  4. const 참조는 임시 매개 변수를 승인 할 수 있습니다.
  5. 임시 객체의 수명을 연장시키는 로컬 const 참조

이러한 규칙을 고려하여 현재 규칙은 다음과 같습니다.

  • 함수 범위 내에서 로컬로 사용될 매개 변수에 대한 참조를 사용하십시오.
  • 0 (널)이 허용 가능한 매개 변수 값이거나 추가 사용을 위해 매개 변수를 저장해야하는 경우 포인터를 사용하십시오. 0 (널)이 허용되는 경우 매개 변수에 "_n"접미사를 추가하고 보호 포인터 (Qt의 QPointer와 같은)를 사용하거나 문서화하십시오. 스마트 포인터를 사용할 수도 있습니다. 일반적인 포인터보다 공유 포인터에 더주의를 기울여야합니다 (그렇지 않으면 디자인 메모리 누수와 책임 혼란으로 끝날 수 있습니다).

다음은 몇 가지 지침입니다.

함수는 전달 된 데이터를 수정하지 않고 사용합니다.

  1. 기본 제공 데이터 형식이나 작은 구조와 같이 데이터 개체가 작 으면 값으로 전달하십시오.

  2. 데이터 객체가 배열 인 경우 유일한 선택이기 때문에 포인터를 사용하십시오. 포인터를 const에 대한 포인터로 만듭니다.

  3. 데이터 객체의 크기가 적당한 구조라면 const 포인터 나 const 참조를 사용하여 프로그램 효율성을 높이고 구조 나 클래스 디자인을 복사하는 데 필요한 시간과 공간을 절약하십시오. 포인터 또는 참조를 const로 만드십시오.

  4. 데이터 객체가 클래스 객체 인 경우 const 참조를 사용합니다. 클래스 디자인의 시맨틱은 종종 참조를 사용해야하는데, 이는 C ++이이 기능을 추가 한 주된 이유이므로 클래스 객체 인수를 전달하는 표준 방법은 참조입니다.

함수는 호출 함수의 데이터를 수정합니다.

1. 데이터 개체가 내장 데이터 형식 인 경우 포인터를 사용하십시오. x가 int 인 fixit (& x)와 같은 코드를 발견하면이 함수가 x를 수정하려고한다는 것이 분명합니다.

2. 데이터 객체가 배열이면 포인터 만 선택하십시오.

3. 데이터 개체가 구조 인 경우 참조 또는 포인터를 사용하십시오.

데이터 개체가 클래스 개체 인 경우 참조를 사용하십시오.

물론 이것은 지침 일 뿐이며 다른 선택을해야하는 이유가있을 수 있습니다. 예를 들어, cin은 기본 유형에 대한 참조를 사용하므로 cin >> & n 대신 cin >> n을 사용할 수 있습니다.


참조는 더 깨끗하고 사용하기 쉬우 며 정보를 숨기는 것이 더 좋습니다. 그러나 참조는 재 할당 할 수 없습니다. 먼저 한 객체를 가리킨 다음 다른 객체를 가리켜 야하는 경우 포인터를 사용해야합니다. 참조는 null 일 수 없으므로 문제의 개체가 null 일 가능성이있는 경우 참조를 사용해서는 안됩니다. 포인터를 사용해야합니다. 객체 조작을 직접 처리하려면, 즉 스택이 아닌 힙의 객체에 메모리 공간을 할당하려면 포인터를 사용해야합니다

int *pInt = new int; // allocates *pInt on the Heap

올바르게 작성된 예제는 다음과 같아야합니다.

void add_one(int& n) { n += 1; }
void add_one(int* const n)
{
  if (n)
    *n += 1;
}

그렇기 때문에 가능한 경우 참조가 바람직합니다 ...


그냥 내 입소문을 넣어. 난 그냥 테스트를 수행했습니다. 그거 참 재밌 네요 g ++에서 참조를 사용하는 것보다 포인터를 사용하여 동일한 미니 프로그램의 어셈블리 파일을 만들도록했습니다. 출력을 볼 때 정확히 동일합니다. 심볼 명 이외의 것. 따라서 간단한 예제에서 성능을 살펴보면 아무런 문제가 없습니다.

이제 포인터 대 참조 주제에 대해 IMHO 나는 명확성이 무엇보다 중요하다고 생각합니다. 암시 적 동작을 읽 자마자 발가락이 말리기 시작합니다. 참조가 NULL이 될 수없는 것은 암묵적인 동작이라는 것에 동의합니다.

NULL 포인터를 역 참조하는 것은 문제가되지 않습니다. 응용 프로그램이 중단되고 쉽게 디버깅 할 수 있습니다. 더 큰 문제는 유효하지 않은 값을 포함하는 초기화되지 않은 포인터입니다. 이것은 명백한 출처없이 정의되지 않은 동작을 일으키는 메모리 손상을 일으킬 가능성이 높습니다.

이것이 참조가 포인터보다 훨씬 안전하다고 생각하는 곳입니다. 그리고 나는 인터페이스 (명확하게 문서화되어야하고, 계약에 의한 디자인 참조, Bertrand Meyer)가 매개 변수의 결과를 함수에 정의한다는 이전 진술에 동의합니다. 이제이 모든 것을 고려하여 내 환경 설정은 가능한 한 언제 어디서나 참조를 사용합니다.


포인터의 경우 무언가를 가리켜 야하므로 포인터에 메모리 공간이 필요합니다.

예를 들어 정수 포인터를 사용하는 함수는 정수 변수를 사용하지 않습니다. 따라서 먼저 함수에 전달할 포인터를 작성해야합니다.

참고로 메모리 비용은 들지 않습니다. 정수 변수가 있으며이를 참조 변수로 전달할 수 있습니다. 그게 다야. 이를 위해 특별히 참조 변수를 만들 필요는 없습니다.

참고 URL : https://stackoverflow.com/questions/7058339/when-to-use-references-vs-pointers



반응형