C ++에서 객체를 함수에 전달하는 방법은 무엇입니까?


249

저는 C ++ 프로그래밍을 처음 사용하지만 Java에 경험이 있습니다. C ++에서 객체를 함수에 전달하는 방법에 대한 지침이 필요합니다.

포인터, 참조 또는 비 포인터 및 비 참조 값을 전달해야합니까? Java에서는 객체에 대한 참조를 보유하는 변수 만 전달하기 때문에 그러한 문제가 없음을 기억합니다.

각 옵션을 사용할 위치를 설명 할 수 있다면 좋을 것입니다.


6
어떤 책에서 C ++을 배우고 있습니까?

17
그 책은 강력히 권장 되지 않습니다. Stan Lippman의 C ++ Primer로 이동하십시오.
Prasoon Saurav

23
글쎄, 당신의 문제가 있습니다. Schildt는 기본적으로 cr * p입니다. Koenig & Moo의 Accelerated C ++를 다운로드하십시오.

9
Bjarne Stroustrup의 C ++ 프로그래밍 언어를 언급 한 사람이 아무도 없는지 궁금합니다. Bjarne Stroustrup은 C ++의 제작자입니다. C ++을 배우기에 정말 좋은 책입니다.
George

15
@George : TC ++ PL은 초보자를위한 것이 아니라 C ++. xD를위한 성경으로 여겨집니다
Prasoon Saurav

답변:


277

C ++ 11 의 경험 법칙 :

패스 값으로 , 경우를 제외하고

  1. 객체의 소유권이 필요하지 않으며 간단한 별칭이 필요합니다.이 경우 참조로 전달됩니다const .
  2. 객체를 변경해야하며,이 경우 lvalue 가 아닌 const참조로 전달을 사용 하십시오 .
  3. 파생 클래스의 객체를 기본 클래스로 전달합니다 . 이 경우 참조전달 해야합니다 . (이전 규칙을 사용하여 const참조 로 전달할지 여부를 결정하십시오 .)

포인터로 전달하는 것은 사실상 권장되지 않습니다. 선택적 매개 변수는 std::optional( boost::optional이전 std 라이브러리의 경우) 로 가장 잘 표현되며 앨리어싱은 참조로 잘 수행됩니다.

C ++ 11의 이동 의미론은 복잡한 객체에 대해서도 가치에 의한 전달 및 반환을 훨씬 더 매력적으로 만듭니다.


C ++ 03 의 경험 법칙 :

인수를 전달 하여 const참조 , 경우를 제외하고

  1. 이들은 함수 내에서 변경할 수 있으며 이러한 변경하는 경우는, 외부에 반영되어야 비 통과 const기준
  2. 이 함수는 인수없이 호출 할 수 있어야합니다.이 경우 포인터로 전달하여 사용자가 NULL/ 0/를 nullptr대신 전달할 수 있습니다 . 인수 에 대한 포인터로 전달 해야하는지 여부를 판별하기 위해 이전 규칙을 적용하십시오.const
  3. 그것들은 빌트인 타입 으로 복사전달 될 수 있습니다
  4. 그들이 함수 내에서 변경할 수 있으며 그러한 변화가 있어야 하지 당신이 할 수있는 경우 외부 반영 사본 패스 (대안은 이전 규칙에 따라 전달하고 기능의 복사 내부를 확인하는 것입니다)

(여기서 "값으로 전달"은 "복사로 전달"이라고합니다. 값으로 전달하면 항상 C ++ 03에서 사본이 작성되므로)


이것에 더 많은 것이 있지만,이 소수의 초보자 규칙은 당신을 꽤 멀리 이끌어 줄 것입니다.


17
+1-또한 일부 (예 : Google)는 함수 내에서 변경 될 객체가 비 const 참조가 아닌 포인터를 통해 전달되어야한다고 생각합니다. 객체의 주소가 함수에 전달 될 때 상기 함수가 그것을 변경할 수 있다는 것이 더 분명하다. 예 : 참조를 사용하면 호출은 foo (bar)입니다. 참조가 const인지 아닌지에 대한 포인터로 foo (& bar); foo가 변경 가능한 객체로 전달되고 있음이 더 분명합니다.
RC.

19
@RC 여전히 포인터가 const인지 아닌지를 알려주지 않습니다. IMHO와 같은 다양한 C ++ 온라인 커뮤니티에서 많은 결함에 대해 Google의 가이드 라인이 마련되었습니다.

14
다른 맥락에서 구글이 길을 이끌고 있지만 C ++에서는 스타일 가이드가 그렇게 좋지 않습니다.
David Rodríguez-dribeas

4
@ArunSaha : 순수 스타일 가이드 인 Stroustrup은 항공 우주 회사를 위해 개발 된 가이드를 제공 합니다. Google 가이드를 탐색했지만 몇 가지 이유로 마음에 들지 않았습니다. Sutter & Alexandrescu C ++ 코딩 표준 은 읽을만한 훌륭한 책이며 훌륭한 조언을 많이 얻을 수 있지만 실제로는 스타일 가이드 가 아닙니다 . 나는 인간과 상식 이외의 스타일 에 대한 자동 검사기를 모른다 .
David Rodríguez-dribeas

3
@anon 그러나 포인터를 통해 인수가 전달되지 않으면 변경되지 않는다는 보장이 있습니다. 그것은 매우 귀중한 IMHO입니다. 그렇지 않으면 함수의 변수에 발생하는 것을 추적하려고 할 때 전달 된 모든 함수의 헤더 파일을 검사하여 변경 여부를 결정해야합니다. 이런 식으로 포인터를 통해 전달 된 항목 만 살펴 봐야합니다.
smehmood

107

C ++ 및 Java의 호출 규칙에는 몇 가지 차이점이 있습니다. C ++에는 기술적으로 값으로 전달 및 참조로 전달의 두 가지 규칙 만 있습니다. 일부 문헌에서는 포인터로 세 번째 전달 규칙 (실제로 포인터 유형의 값으로 전달)을 포함합니다. 또한 인수의 유형에 constness를 추가하여 의미를 향상시킬 수 있습니다.

참조로 전달

참조로 전달한다는 것은 함수가 객체 인스턴스를 개념적으로 수신하지만 사본이 아니라는 것을 의미합니다. 참조는 개념적으로 호출 컨텍스트에서 사용 된 오브젝트의 별명이며 널이 될 수 없습니다. 함수 내부에서 수행 된 모든 작업은 함수 외부의 개체에 적용됩니다. 이 규칙은 Java 또는 C에서 사용할 수 없습니다.

값으로 전달 (및 포인터로 전달)

컴파일러는 호출 컨텍스트에서 객체의 복사본을 생성하고 해당 복사본을 함수 내에서 사용합니다. 함수 내부에서 수행되는 모든 작업은 외부 요소가 아닌 복사본에 수행됩니다. 이것은 Java의 기본 유형에 대한 규칙입니다.

특별한 버전은 포인터를 (객체의 주소) 함수에 전달합니다. 이 함수는 포인터를 수신하고 포인터 자체에 적용된 모든 작업은 복사 (포인터)에 적용되며, 역 참조 된 포인터에 적용된 작업은 해당 메모리 위치의 객체 인스턴스에 적용됩니다. 부작용이있을 수 있습니다. 객체에 대한 포인터의 값별 전달을 사용하면 내부 함수가 참조 별과 같이 외부 값을 수정하고 선택적 값을 허용 할 수 있습니다 (널 포인터 전달).

이것은 함수가 외부 변수를 수정해야 할 때 C에서 사용되는 규칙이며 Java에서 참조 유형으로 사용되는 규칙입니다. 참조는 복사되지만 참조 된 오브젝트는 동일합니다. 기능이지만 지정된 메모리로 변경됩니다.

방정식에 const 추가

C ++에서는 변수, 포인터 및 참조를 다른 수준으로 정의 할 때 객체에 상수를 할당 할 수 있습니다. 변수를 상수로 선언하고 상수 인스턴스에 대한 참조를 선언 할 수 있으며 상수 객체에 대한 모든 포인터, 가변 객체에 대한 상수 포인터 및 상수 요소에 대한 상수 포인터를 정의 할 수 있습니다. 반대로 Java에서는 한 수준의 상수 (최종 키워드) 만 정의 할 수 있습니다. 변수의 레벨 (기본 유형의 인스턴스, 참조 유형의 참조)은 변경할 수 없습니다 (클래스 자체가 아닌 한) 불변).

이것은 C ++ 호출 규칙에서 광범위하게 사용됩니다. 객체가 작 으면 값으로 객체를 전달할 수 있습니다. 컴파일러는 사본을 생성하지만 그 사본은 값 비싼 작업이 아닙니다. 다른 유형의 경우 함수가 객체를 변경하지 않으면 유형의 상수 인스턴스 (일반적으로 상수 참조)에 대한 참조를 전달할 수 있습니다. 이것은 객체를 복사하지 않지만 함수에 전달합니다. 그러나 동시에 컴파일러는 객체가 함수 내에서 변경되지 않도록 보장합니다.

엄지 손가락의 규칙

다음은 몇 가지 기본 규칙입니다.

  • 기본 유형에 대한 값별 패스 선호
  • 다른 유형의 상수에 대한 참조를 통한 참조를 선호
  • 함수가 인수를 수정해야하는 경우 참조 기준을 사용하십시오.
  • 인수가 선택적인 경우, 패스 바이 포인터를 사용하십시오 (선택적 값을 수정하지 않아야하는 경우 상수로).

이 규칙과 다른 작은 편차가 있는데, 첫 번째는 객체의 소유권을 처리하는 것입니다. 객체에 new를 동적으로 할당 할 때는 delete (또는 [] 버전)로 할당을 해제해야합니다. 객체의 파괴를 담당하는 객체 또는 기능은 리소스의 소유자로 간주됩니다. 동적으로 할당 된 객체가 코드 조각으로 생성되지만 소유권이 다른 요소로 전송되는 경우 일반적으로 포인터 별 의미 체계 또는 가능한 경우 스마트 포인터로 수행됩니다.

사이드 노트

C ++과 Java 참조의 차이점의 중요성을 주장하는 것이 중요합니다. C ++에서 참조는 개념적으로 개체에 대한 접근자가 아니라 개체의 인스턴스입니다. 가장 간단한 예는 스왑 기능을 구현하는 것입니다.

// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
   Type tmp = a;
   a = b;
   b = tmp;
}
int main() {
   Type a, b;
   Type old_a = a, old_b = b;
   swap( a, b );
   assert( a == old_b );
   assert( b == old_a ); 
}

위의 swap 함수 는 참조를 사용하여 두 인수를 모두 변경 합니다. 자바에서 가장 가까운 코드 :

public class C {
   // ...
   public static void swap( C a, C b ) {
      C tmp = a;
      a = b;
      b = tmp;
   }
   public static void main( String args[] ) {
      C a = new C();
      C b = new C();
      C old_a = a;
      C old_b = b;
      swap( a, b ); 
      // a and b remain unchanged a==old_a, and b==old_b
   }
}

코드의 Java 버전은 참조 사본을 내부적으로 수정하지만 실제 오브젝트를 외부 적으로 수정하지는 않습니다. Java 참조는 값으로 함수에 전달되는 포인터 산술이없는 C 포인터입니다.


4
@ david-rodriguez-dribeas 저는 엄지 섹션의 규칙, 특히 "기본 유형에 대한 값을 통과하는 것을 선호합니다"
yadab

나에 따르면, 이것은 질문에 대한 더 나은 대답입니다.
unrealsoul007

22

고려해야 할 몇 가지 경우가 있습니다.

매개 변수 수정 ( "out"및 "in / out"매개 변수)

void modifies(T &param);
// vs
void modifies(T *param);

이 경우는 주로 스타일에 관한 것입니다. 코드를 call (obj) 또는 call (& obj) 처럼 보이게 하시겠습니까? 그러나 차이점이 중요한 두 가지 점이 있습니다. 아래의 선택적 경우와 연산자를 오버로드 할 때 참조를 사용하려고합니다.

... 및 선택 사항

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);

매개 변수가 수정되지 않았습니다

void uses(T const &param);
// vs
void uses(T param);

흥미로운 사례입니다. 경험상 "복사 가능"유형은 값으로 전달됩니다. 일반적으로 작은 유형이지만 항상 그렇지는 않습니다. 반면에 다른 유형은 const ref로 전달됩니다. 그러나 함수 내에서 사본을 복사해야 할 경우 value로 전달해야합니다 . (예, 이것은 약간의 구현 세부 사항을 노출시킵니다. C'est le C ++. )

... 및 선택 사항

void uses(T const *param=0);  // default value optional, too
// vs
void uses();
void uses(T const &param);  // or optional(T param)

여기에는 모든 상황에서 가장 작은 차이가 있으므로 인생을 가장 편하게 만드는 것을 선택하십시오.

값별 상수는 구현 세부 사항입니다.

void f(T);
void f(T const);

이 선언은 실제로 정확히 동일한 기능입니다! 값으로 전달할 때 const는 순전히 구현 세부 사항입니다. 사용해보십시오 :

void f(int);
void f(int const) { /* implements above function, not an overload */ }

typedef void NC(int);       // typedefing function types
typedef void C(int const);

NC *nc = &f;  // nc is a function pointer
C *c = nc;    // C and NC are identical types

3
+1 const가치를 지날 때 구현되는 것에 대해 몰랐습니다 .
balki

20

값으로 전달 :

void func (vector v)

함수가 환경으로부터 완전히 분리되어야하는 경우, 즉 함수가 원래 변수를 수정하지 못하게하고 함수가 실행되는 동안 다른 스레드가 값을 수정하지 못하도록 값으로 변수를 전달하십시오.

단점은 CPU주기와 객체를 복사하는 데 사용 된 추가 메모리입니다.

const 참조로 전달하십시오.

void func (const vector& v);

이 양식은 복사 오버 헤드를 제거하면서 값별 동작을 에뮬레이트합니다. 이 함수는 원래 객체에 대한 읽기 권한을 갖지만 값을 수정할 수는 없습니다.

단점은 스레드 안전성입니다. 다른 스레드에 의해 원래 객체에 대한 모든 변경 사항은 여전히 ​​실행되는 동안 함수 내에 표시됩니다.

비 const 참조로 전달하십시오.

void func (vector& v)

함수가 변수에 값을 다시 써야 할 때 이것을 사용하십시오. 이는 궁극적으로 호출자가 사용합니다.

const 참조 사례와 마찬가지로 스레드 안전하지 않습니다.

const 포인터로 전달하십시오.

void func (const vector* vp);

다른 구문을 제외하고는 const-reference에 의한 전달과 기능적으로 동일하며, 호출하는 함수가 전달할 유효한 데이터가 없음을 표시하기 위해 NULL 포인터를 전달할 수 있다는 사실.

스레드 안전하지 않습니다.

비 const 포인터로 전달하십시오.

void func (vector* vp);

비 const 참조와 유사합니다. 호출자는 일반적으로 함수가 값을 다시 쓰지 않을 때 변수를 NULL로 설정합니다. 이 규칙은 많은 glibc API에서 볼 수 있습니다. 예:

void func (string* str, /* ... */) {
    if (str != NULL) {
        *str = some_value; // assign to *str only if it's non-null
    }
}

모든 스레드가 안전하지 않은 참조 / 포인터를 통과하는 것처럼.


0

아무도 언급하지 않았으므로 객체를 c ++의 함수에 전달할 때 객체의 복제본을 생성 한 다음 객체에 복제본이없는 경우 객체의 기본 복사 생성자가 호출됩니다. 원본 객체 대신 객체의 사본에 반영되는 객체 값을 변경하면 c ++의 문제입니다. 따라서 모든 클래스 속성을 포인터로 만들면 복사 생성자가 주소를 복사합니다. 포인터 속성, 포인터 속성 주소에 저장된 값을 조작하는 객체의 메소드 호출시 변경 사항도 매개 변수로 전달되는 원래 객체에 반영되므로 동일한 Java로 작동 할 수 있지만 모든 클래스를 잊지 마십시오. 속성은 포인터 여야하며 포인터 값을 변경해야합니다.코드 설명으로 훨씬 명확해질 것입니다.

Class CPlusPlusJavaFunctionality {
    public:
       CPlusPlusJavaFunctionality(){
         attribute = new int;
         *attribute = value;
       }

       void setValue(int value){
           *attribute = value;
       }

       void getValue(){
          return *attribute;
       }

       ~ CPlusPlusJavaFuncitonality(){
          delete(attribute);
       }

    private:
       int *attribute;
}

void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
   int* prt = obj.attribute;
   *ptr = value;
}

int main(){

   CPlusPlusJavaFunctionality obj;

   obj.setValue(10);

   cout<< obj.getValue();  //output: 10

   changeObjectAttribute(obj, 15);

   cout<< obj.getValue();  //output: 15
}

그러나 메모리 누수가 발생하고 소멸자를 호출하는 것을 잊지 않는 포인터와 관련된 많은 코드 작성을 끝내기 때문에 이것은 좋은 생각이 아닙니다. 그리고이 C ++에는 포인터를 포함하는 객체가 함수 인수에 전달되어 다른 객체 데이터 조작을 중지 할 때 새 메모리를 생성하는 복사 생성자가 있습니다 .Java는 값으로 전달하고 값은 참조이므로 복사 생성자가 필요하지 않습니다.


-1

객체를 매개 변수로 함수에 전달하는 방법에는 세 가지가 있습니다.

  1. 참조로 전달
  2. 가치를지다
  3. 매개 변수에 상수 추가

다음 예를 살펴보십시오.

class Sample
{
public:
    int *ptr;
    int mVar;

    Sample(int i)
    {
        mVar = 4;
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }

    void PrintVal()
    {
        cout << "The value of the pointer is " << *ptr << endl
             << "The value of the variable is " << mVar;
   }
};

void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}


int main()
{

  Sample s1= 10;
  SomeFunc(s1);
  s1.PrintVal();
  char ch;
  cin >> ch;
}

산출:

내가 someFunc
에 있다고 포인터
의 값 은 -17891602 입니다. 변수의 값은 4입니다.


두 가지 방법 만 있습니다 (처음 언급 한 두 가지 방법). "매개 변수에 상수 전달"의 의미를 모릅니다
MM

-1

다음은 C ++에서 작동하도록 인수 / 매개 변수를 전달하는 방법입니다.

1. 가치.

// passing parameters by value . . .

void foo(int x) 
{
    x = 6;  
}

2. 참고로.

// passing parameters by reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  
}

// passing parameters by const reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  // compile error: a const reference cannot have its value changed!
}

3. 개체 별.

class abc
{
    display()
    {
        cout<<"Class abc";
    }
}


// pass object by value
void show(abc S)
{
    cout<<S.display();
}

// pass object by reference
void show(abc& S)
{
    cout<<S.display();
}

1
"패스 바이 패스"는 문제가되지 않습니다. 값으로 전달하고 참조로 전달합니다. "case 3"은 실제로 값을 기준으로 한 건의 사례를 보여주고 참조로 한 건의 사례를 보여줍니다.
MM
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.