값으로 전달합니다. 참조 및 값으로 매개변수를 전달합니다. 기본 설정

따라서 Factorial(n)을 숫자 n의 계승을 계산하는 함수라고 하겠습니다. 그런 다음 계승 1이 1이라는 것을 "알고" 있다고 가정하면 다음 체인을 구성할 수 있습니다.

계승(4)=계수(3)*4

계승(3)=계수(2)*3

계승(2)=계수(1)*2

그러나 n=1일 때 Factorial 함수가 1을 반환해야 한다는 최종 조건이 없다면 그러한 이론적인 체인은 결코 끝나지 않았을 것이며 이는 호출 스택 오버플로 오류(호출 스택 오버플로)일 수 있습니다. 호출 스택이 무엇이고 어떻게 오버플로될 수 있는지 이해하기 위해 함수의 재귀 구현을 살펴보겠습니다.

함수 계승(n: 정수): LongInt;

n=1이면

계승:=Factorial(n-1)*n;

끝;

보시다시피, 체인이 올바르게 작동하려면 다음 함수가 자체적으로 호출되기 전에 모든 지역 변수를 어딘가에 저장해야 합니다. 그러면 체인이 반전될 때 결과가 정확합니다(계산된 값 n-1의 계승에 n을 곱합니다). 우리의 경우 팩토리얼 함수가 자체적으로 호출될 때마다 변수 n의 모든 값을 저장해야 합니다. 함수 자신을 재귀적으로 호출할 때 함수의 지역 변수가 저장되는 영역을 호출 스택이라고 합니다. 물론 이 스택은 무한하지 않으며 재귀 호출이 잘못 구성되면 소진될 수 있습니다. 예제 반복의 유한성은 n=1일 때 함수 호출이 중지된다는 사실로 보장됩니다.

값 및 참조로 매개변수 전달

지금까지는 서브루틴의 값을 변경할 수 없었습니다. 실제 매개변수(즉, 서브루틴을 호출할 때 지정되는 매개변수), 일부 응용 작업에서는 이것이 편리할 것입니다. 두 개의 실제 매개변수 값을 한 번에 변경하는 Val 프로시저를 기억해 봅시다. 첫 번째는 문자열 변수의 변환된 값이 기록될 매개변수이고, 두 번째는 잘못된 매개변수의 수가 기록되는 Code 매개변수입니다. 타입변환시 실패할 경우를 대비하여 문자가 배치됩니다. 저것들. 서브루틴이 실제 매개변수를 변경할 수 있는 메커니즘이 여전히 있습니다. 이는 매개변수를 전달하는 다양한 방법 덕분에 가능합니다. 이러한 방법을 자세히 살펴보겠습니다.

파스칼로 프로그래밍하기

값으로 매개변수 전달

기본적으로 이것이 모든 매개변수를 루틴에 전달하는 방법입니다. 메커니즘은 다음과 같습니다. 실제 매개변수가 지정되면 해당 값이 서브루틴이 있는 메모리 영역에 복사되고 함수나 프로시저가 작업을 완료한 후 이 영역이 지워집니다. 대략적으로 말하면, 서브루틴이 실행되는 동안 해당 매개변수의 복사본이 두 개 있습니다. 하나는 호출 프로그램 범위에 있고 다른 하나는 함수 범위에 있습니다.

이 매개변수 전달 방법을 사용하면 호출 자체 외에도 모든 실제 매개변수의 모든 값을 복사해야 하므로 서브루틴을 호출하는 데 더 많은 시간이 걸립니다. 서브루틴에 많은 양의 데이터가 전달되는 경우(예: 요소 수가 많은 배열) 데이터를 로컬 영역에 복사하는 데 소요되는 시간이 상당할 수 있으므로 프로그램 개발 및 개발 시 이 점을 고려해야 합니다. 성능의 병목 현상을 찾아냅니다.

이 전송 방법을 사용하면 실제 매개변수가 서브루틴에 의해 변경될 수 없습니다. 변경 사항은 기능이나 절차가 완료된 후 해제되는 격리된 로컬 영역에만 영향을 미치기 때문입니다.

참조로 매개변수 전달

이 방법을 사용하면 실제 매개변수의 값이 서브루틴에 복사되지 않지만 해당 값이 위치한 메모리의 주소(변수에 대한 링크)가 전송됩니다. 이 경우 서브루틴은 이미 로컬 범위에 없는 값을 변경하고 있으므로 모든 변경 사항이 호출 프로그램에 표시됩니다.

인수가 참조로 전달되어야 함을 나타내기 위해 선언 앞에 var 키워드를 추가합니다.

절차 getTwoRandom(var n1, n2:Integer; 범위: Integer);

n1:=무작위(범위);

n2:=무작위(범위); 끝 ;

var rand1, rand2: 정수;

시작하다 getTwoRandom(rand1,rand2,10); WriteLn(rand1); WriteLn(rand2);

끝.

이 예에서는 두 변수에 대한 참조가 실제 매개변수인 rand1 및 rand2로 getTwoRandom 프로시저에 전달됩니다. 세 번째 실제 매개변수(10)는 값으로 전달됩니다. 프로시저는 형식 매개변수를 사용하여 작성합니다.

문자열을 사용한 프로그래밍 방법

실험실 작업의 목적 : C# 언어의 메서드, 문자 데이터 작업 규칙 및 ListBox 구성 요소를 알아봅니다. 문자열을 다루는 프로그램을 작성하세요.

행동 양식

메소드는 프로그램 코드를 포함하는 클래스 요소입니다. 이 메서드의 구조는 다음과 같습니다.

[속성] [지정자] 유형 이름([매개변수])

메소드 본문;

속성은 메소드의 속성에 대해 컴파일러에 보내는 특별한 지침입니다. 속성은 거의 사용되지 않습니다.

한정자는 다양한 용도로 사용되는 키워드입니다. 예를 들면 다음과 같습니다.

· 다른 클래스에 대한 메소드의 가용성 결정:

영형 사적인– 메소드는 이 클래스 내에서만 사용할 수 있습니다.

영형 보호됨– 이 방법은 하위 클래스에서도 사용할 수 있습니다.

영형 공공의– 이 클래스에 접근할 수 있는 다른 모든 클래스에서 해당 메서드를 사용할 수 있습니다.

클래스를 생성하지 않고 메소드의 가용성 표시

· 설정 유형

유형은 메서드가 반환하는 결과를 결정합니다. 이는 C#에서 사용 가능한 모든 유형일 수 있으며, 결과가 필요하지 않은 경우 void 키워드도 될 수 있습니다.

메소드 이름은 메소드를 호출하는 데 사용되는 식별자입니다. 변수 이름과 마찬가지로 식별자에도 동일한 요구 사항이 적용됩니다. 즉, 문자, 숫자, 밑줄로 구성될 수 있지만 숫자로 시작할 수는 없습니다.

매개변수는 호출 시 메소드에 전달될 수 있는 변수 목록입니다. 각 매개변수는 변수 유형과 이름으로 구성됩니다. 매개변수는 쉼표로 구분됩니다.

메소드의 본문은 다른 메소드, 클래스, 네임스페이스 등의 정의를 포함할 수 없다는 점을 제외하면 일반 프로그램 코드입니다. 메소드가 일부 결과를 반환해야 하는 경우 return 키워드는 반환 값과 함께 끝에 있어야 합니다. . 결과를 반환할 필요가 없으면 return 키워드를 사용할 수는 있지만 사용할 필요는 없습니다.

표현식을 평가하는 메소드의 예:

공개 이중 계산(더블 a, 더블 b, 더블 c)

return Math.Sin(a) * Math.Cos(b);

double k = Math.Tan(a * b);

return k * Math.Exp(c / k);

메소드 오버로딩

C# 언어를 사용하면 이름은 같지만 매개변수는 다른 여러 메서드를 만들 수 있습니다. 컴파일러는 프로그램을 빌드할 때 가장 적절한 방법을 자동으로 선택합니다. 예를 들어 숫자를 거듭제곱하는 두 가지 별도의 메서드를 작성할 수 있습니다. 한 알고리즘은 정수에 사용되고 다른 알고리즘은 실수에 사용됩니다.

///

/// 정수에 대해 X를 Y의 거듭제곱으로 계산합니다.

///

private int Pow(int X, int Y)

///

/// 실수에 대해 X를 Y의 거듭제곱으로 계산합니다.

///

private double Pow(더블 X, 더블 Y)

return Math.Exp(Y * Math.Log(Math.Abs(X)));

그렇지 않으면 (Y == 0)

이 코드는 동일한 방식으로 호출되며 유일한 차이점은 매개변수에 있습니다. 첫 번째 경우 컴파일러는 정수 매개변수를 사용하여 Pow 메소드를 호출하고 두 번째 경우에는 실제 매개변수를 사용하여 호출합니다.

기본 설정

버전 4.0(Visual Studio 2010)부터 C# 언어에서는 일부 매개 변수에 대해 기본값을 설정할 수 있으므로 메서드 호출 시 일부 매개 변수를 생략할 수 있습니다. 이렇게 하려면 메소드를 구현할 때 필수 매개변수에 매개변수 목록에서 직접 값을 할당해야 합니다.

private void GetData(int Number, int 선택사항 = 5 )

Console.WriteLine("번호: (0)", 번호);

Console.WriteLine("선택 사항: (0)", 선택 사항);

이 경우 다음과 같이 메소드를 호출할 수 있습니다.

GetData(10, 20);

첫 번째 경우 Optional 매개변수는 명시적으로 지정되었으므로 20이 되고, 두 번째 경우에는 5가 됩니다. 명시적으로 지정되지 않으며 컴파일러는 기본값을 사용합니다.

기본 매개변수는 매개변수 목록의 오른쪽에만 설정할 수 있습니다. 예를 들어 이러한 메소드 서명은 컴파일러에서 허용되지 않습니다.

개인 무효 GetData(int 선택 사항 = 5 , 정수 숫자)

매개변수가 일반적인 방법으로(추가 ref 및 out 키워드 없이) 메소드에 전달되면 메소드 내 매개변수에 대한 변경사항은 기본 프로그램의 해당 값에 영향을 주지 않습니다. 다음과 같은 방법이 있다고 가정해 보겠습니다.

개인 무효 Calc(int Number)

메소드 내부에서 매개변수로 전달된 Number 변수가 변경되는 것을 확인할 수 있습니다. 메소드를 호출해 보겠습니다.

Console.WriteLine(n);

숫자 1이 화면에 나타납니다. 즉, Calc 메서드에서 변수가 변경되었음에도 불구하고 기본 프로그램의 변수 값은 변경되지 않았습니다. 이는 메소드가 호출될 때 복사변수가 전달되면 메소드가 변경되는 것은 바로 이 변수입니다. 메서드가 종료되면 복사본의 값이 손실됩니다. 매개변수를 전달하는 이 방법을 호출합니다. 값으로 전달.

메소드가 전달된 변수를 변경하려면 ref 키워드와 함께 전달되어야 합니다. 이는 메소드 시그니처와 호출 시 모두에 있어야 합니다.

private void Calc(ref int Number)

Console.WriteLine(n);

이 경우 화면에 숫자 10이 나타납니다. 메서드 값의 변경은 기본 프로그램에도 영향을 미쳤습니다. 이 메소드 전송을 참조로 전달, 즉. 더 이상 전송되는 복사본이 아니라 메모리의 실제 변수에 대한 참조입니다.

메소드가 값을 반환하기 위해서만 참조로 변수를 사용하고 처음에 그 안에 무엇이 있었는지 상관하지 않는 경우 해당 변수를 초기화할 수 없지만 out 키워드를 사용하여 전달합니다. 컴파일러는 변수의 초기 값이 중요하지 않다는 것을 이해하고 초기화 부족에 대해 불평하지 않습니다.

private void Calc(out int Number)

int n; // 우리는 아무것도 할당하지 않습니다!

문자열 데이터 유형

C# 언어는 문자열 유형을 사용하여 문자열을 저장합니다. 문자열 변수를 선언하고 일반적으로 즉시 초기화하려면 다음 코드를 작성하면 됩니다.

문자열 a = "텍스트";

문자열 b = "문자열";

줄에 추가 작업을 수행할 수 있습니다. 이 경우 한 줄의 텍스트가 다른 줄의 텍스트에 추가됩니다.

문자열 c = a + " " + b; // 결과: 문자열 텍스트

문자열 유형은 실제로 문자열 클래스에 대한 별칭이며 이를 통해 문자열에 대해 더 복잡한 작업을 수행할 수 있습니다. 예를 들어 IndexOf 메서드는 문자열에서 부분 문자열을 검색할 수 있고 Substring 메서드는 지정된 위치에서 시작하여 지정된 길이의 문자열 부분을 반환합니다.

문자열 a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

int index = a.IndexOf("OP"); // 결과: 14 (0부터 세기)

문자열 b = a.Substring(3, 5); // 결과: DEFGH

문자열에 특수 문자를 추가해야 하는 경우 백슬래시로 시작하는 이스케이프 시퀀스를 사용하면 됩니다.

ListBox 구성 요소

요소 리스트 박스키보드나 마우스를 사용하여 요소를 선택한 목록입니다. 요소 목록은 속성에 의해 지정됩니다. 품목. 항목은 고유한 속성과 메서드를 갖는 요소입니다. 행동 양식 추가하다, 제거 위치그리고 끼워 넣다요소를 추가, 삭제, 삽입하는 데 사용됩니다.

객체 품목목록에 개체를 저장합니다. 개체는 모든 클래스일 수 있습니다. 클래스 데이터는 표시하기 위해 ToString 메서드에 의해 문자열 표현으로 변환됩니다. 우리의 경우 문자열은 객체로 작동합니다. 그러나 Items 객체는 object 유형으로 캐스팅된 객체를 저장하므로 이를 사용하기 전에 원래 유형(이 경우 문자열)으로 다시 캐스팅해야 합니다.

문자열 a = (문자열)listBox1.Items;

선택한 요소의 수를 결정하려면 속성을 사용하십시오. SelectedIndex.

C++로 프로그래밍을 시작하고 책과 기사를 집중적으로 공부했을 때 나는 변함없이 같은 조언을 접했습니다. 함수에서 변경되어서는 안되는 객체를 함수에 전달해야 하는 경우 항상 전달해야 합니다. 상수를 참조하여(PPSK), 기본 유형이나 크기가 비슷한 구조를 전달해야 하는 경우는 제외됩니다. 왜냐하면 10년 넘게 C++로 프로그래밍하면서 저는 이 조언을 매우 자주 접했고(저도 한 번 이상 이 조언을 했습니다) 오랫동안 제 마음 속에 "흡수"되었습니다. 상수를 참조하여 모든 인수를 자동으로 전달합니다. . 그러나 시간이 흐르고 이동 의미론을 갖춘 C++11을 사용할 수 있게 된 지 벌써 7년이 지났습니다. 이와 관련하여 좋은 옛 교리에 의문을 제기하는 목소리가 점점 더 많이 들립니다. 많은 사람들이 상수를 참조로 전달하는 것은 과거의 일이며 이제는 필요하다고 주장하기 시작했습니다. 값으로 전달(PPZ). 이 대화의 이면에는 무엇이 있으며, 이 모든 것에서 우리가 끌어낼 수 있는 결론은 무엇인지 이 기사에서 논의하고 싶습니다.

책의 지혜

우리가 준수해야 할 규칙을 이해하려면 책을 살펴 보는 것이 좋습니다. 책은 우리가 받아들일 의무는 없지만 확실히 들어볼 가치가 있는 훌륭한 정보 소스입니다. 그리고 우리는 역사와 기원부터 시작할 것입니다. PPSC의 첫 번째 사과자가 누구인지는 알 수 없으며 PPSC 사용 문제에 대해 개인적으로 나에게 가장 큰 영향을 준 책을 예로 들겠습니다.

메이어스

좋아요, 여기 모든 매개변수가 참조로 전달되는 클래스가 있습니다. 이 클래스에 문제가 있나요? 불행히도 존재하며 이 문제는 표면에 있습니다. 우리 클래스에는 2개의 기능적 엔터티가 있습니다. 첫 번째는 객체 생성 단계에서 값을 가져오고 두 번째는 이전에 설정된 값을 변경할 수 있도록 합니다. 엔터티는 두 개이지만 기능은 네 개입니다. 이제 2개의 유사한 엔터티가 아니라 3, 5, 6을 가질 수 있다고 상상해 보십시오. 그러면 어떻게 될까요? 그러면 우리는 심각한 코드 팽창에 직면하게 될 것입니다. 따라서 대량의 기능을 생성하지 않기 위해 매개변수의 링크를 완전히 포기하자는 제안이 있었습니다.

주형 class 홀더 ( 공개: 명시적 홀더(T 값): m_Value(move(value)) ( ) void setValue(T 값) ( ​​m_Value = move(value); ) const T& value() const noException ( return m_Value; ) 비공개: T m_Value; );

즉시 눈에 띄는 첫 번째 장점은 코드가 훨씬 적다는 것입니다. const 및 &가 제거되었기 때문에 첫 번째 버전보다 그 수가 훨씬 적습니다(move는 추가했지만). 그러나 우리는 참조로 전달하는 것이 값으로 전달하는 것보다 더 생산적이라고 항상 배워왔습니다! C++11 이전에도 그랬고 지금도 그렇습니다. 하지만 이제 이 코드를 보면 첫 번째 버전보다 더 이상 복사가 없다는 것을 알 수 있습니다. T에 이동 생성자가 있는 경우. 저것들. PPSC 자체는 과거에도 PPZ보다 빠르지만 앞으로도 그럴 것입니다. 하지만 코드는 전달된 참조를 어떻게든 사용하고 이 인수가 복사되는 경우가 많습니다.

그러나 이것이 전부는 아닙니다. 복사만 하는 첫 번째 옵션과 달리 여기서는 움직임도 추가합니다. 하지만 이사는 돈이 많이 드는 작업이잖아요? 이 주제에 관해 우리가 고려 중인 Mayers 책에는 "이동 작업이 존재하지 않고, 저렴하지도 않고, 사용되지도 않는다고 가정합니다."라는 제목의 장("항목 29")도 있습니다. 제목에서 주요 아이디어가 명확해야 하지만, 자세한 내용을 원하시면 꼭 읽어보시기 바랍니다. 이에 대해서는 길게 설명하지 않겠습니다.

여기서는 첫 번째 방법과 마지막 방법에 대한 완전한 비교 분석을 수행하는 것이 적절하지만 책에서 벗어나고 싶지 않으므로 다른 섹션에 대한 분석을 연기하고 여기에서는 Scott의 주장을 계속 고려할 것입니다. 그렇다면 세 번째 옵션이 두 번째 옵션보다 확실히 짧다는 사실을 제외하고 Scott은 현대 코드에서 PPSC에 비해 PPZ의 장점이 무엇이라고 생각합니까?

그는 rvalue를 전달하는 경우 즉, 일부 호출은 다음과 같습니다. 홀더 홀더(string("me")); , PPSC 옵션을 사용하면 복사가 가능하고 PPZ 옵션을 사용하면 이동이 가능합니다. 반면에 전송이 다음과 같은 경우: 홀더 홀더(someLvalue); , PPZ는 복사와 이동을 모두 수행하므로 확실히 손실되는 반면, PPSC 버전에서는 복사가 하나만 수행됩니다. 저것들. 순전히 효율성을 고려한다면 PPZ는 코드 양과 이동 의미론에 대한 "완전한"( && 를 통한) 지원 사이의 일종의 절충안인 것으로 밝혀졌습니다.

이것이 바로 Scott이 자신의 조언을 그토록 신중하게 표현하고 이를 매우 신중하게 장려하는 이유입니다. 심지어 그는 압박감을 느끼는 듯 마지못해 그 문제를 꺼낸 것 같았습니다. 그는 책에서 이 주제에 관해 토론을 하지 않을 수 없었습니다. 왜냐하면... 그것은 상당히 광범위하게 논의되었으며 Scott은 항상 집단적 경험의 수집가였습니다. 또한 그는 PPZ를 옹호하는 주장을 거의 하지 않지만 이 "기술"에 의문을 제기하는 주장을 많이 제시합니다. 우리는 이후 섹션에서 반대하는 그의 주장을 살펴보겠지만 여기서는 Scott이 PPP를 옹호하는 주장을 간략하게 반복하겠습니다. “물체가 움직임을 지원하고 가격이 저렴하다면”): rvalue 표현식을 함수 인수로 전달할 때 복사를 방지할 수 있습니다. 하지만 마이어스의 책은 충분히 고통스럽기 때문에 다른 책으로 넘어가겠습니다.

그건 그렇고, 누구든지 책을 읽고 Mayers가 보편적 참조라고 부르는 것(현재는 전달 참조라고 함)에 옵션을 포함하지 않은 것에 놀랐다면 이는 쉽게 설명됩니다. 저는 PPZ와 PPSC만 고려하고 있습니다. 왜냐하면... 두 유형(rvalue/lvalue)의 참조 전달을 지원하기 위해 템플릿이 아닌 메서드에 템플릿 함수를 도입하는 것은 좋지 않은 형태라고 생각합니다. 코드가 달라지고(더 이상 불변성이 없음) 다른 문제가 발생한다는 사실은 말할 것도 없습니다.

Josattis와 회사

마지막으로 살펴볼 책은 이 기사에서 언급된 모든 책 중 가장 최근의 책이기도 한 “C++ 템플릿”입니다. 2017년 말에 출판되었습니다(책 안에 2018년이 표시되어 있습니다). 다른 책과 달리 이 책은 전적으로 패턴에 전념하고 있으며, Stroustrup과 같은 일반적인 조언(Mayers와 같은)이나 C++이 아닙니다. 따라서 여기에서는 템플릿 작성 관점에서 장단점을 고려합니다.

7장 전체가 이 주제에 할애되어 있으며, "가치에 따라 또는 참조에 따라?"라는 설득력 있는 제목이 있습니다. 이 장에서 저자는 모든 전송 방법의 장단점을 매우 간단하지만 간결하게 설명합니다. 효율성에 대한 분석은 실제로 여기에 제공되지 않으며 PPSC가 PPZ보다 빠를 것이라는 것은 당연한 것으로 간주됩니다. 그러나 이 모든 것에 대해 저자는 이 장의 마지막 부분에서 템플릿 기능에 기본 PPP를 사용할 것을 권장합니다. 왜? 링크를 사용하면 템플릿 매개변수가 완전히 표시되고 링크가 없으면 "감소"되어 배열 및 문자열 리터럴 처리에 유익한 효과가 있습니다. 저자는 어떤 유형의 PPP에 대해 효과가 없는 것으로 판명되면 언제든지 std::ref 및 std::cref 를 사용할 수 있다고 믿습니다. 이것은 몇 가지 조언입니다. 솔직히 말해서 위 기능을 사용하고 싶어하는 사람들을 많이 보셨나요?

PPSC에 관해 그들은 무엇을 조언합니까? 성능이 중요하거나 다른 문제가 있는 경우 PPSC를 사용하는 것이 좋습니다. 무거운 PPP를 사용하지 않는 이유 물론 여기서는 상용구 코드에 대해서만 이야기하고 있지만 이 조언은 프로그래머가 10년 동안 가르쳐온 모든 것과 직접적으로 모순됩니다. 이것은 단지 PPP를 대안으로 고려하라는 조언이 아닙니다. 아니요, PPSC를 대안으로 만들라는 조언입니다.

이로써 우리의 도서 여행은 끝났습니다. 왜냐면... 나는 이 문제에 대해 우리가 참고해야 할 다른 책이 없다는 것을 알고 있습니다. 또 다른 미디어 공간으로 넘어가 보겠습니다.

네트워크 지혜

왜냐하면 우리는 인터넷 시대에 살고 있으니 책의 지혜에만 의존해서는 안 됩니다. 게다가 예전에는 책을 쓰던 작가들이 이제는 블로그만 쓰고 책을 버리는 경우도 많다. 이들 저자 중 한 명인 Herb Sutter는 2013년 5월 자신의 블로그 "GotW #4 Solution: Class Mechanics"에 기사를 게시했습니다. 이 기사는 우리가 다루고 있는 문제에 전적으로 전념하지는 않지만 여전히 이에 대해 다루고 있습니다.

따라서 기사의 원래 버전에서 Sutter는 "상수를 참조하여 매개변수를 전달합니다"라는 오래된 지혜를 반복했지만 이 버전의 기사는 더 이상 볼 수 없습니다. 이 기사에는 반대되는 조언이 포함되어 있습니다. 만약에매개변수는 계속 복사된 다음 값으로 전달됩니다.” 다시 악명 높은 "if". Sutter가 기사를 변경한 이유는 무엇이며, 이에 대해 제가 어떻게 알았습니까? 댓글에서. 그의 기사에 대한 댓글을 읽어보세요. 그런데 기사 자체보다 더 흥미롭고 유용합니다. 사실, 기사를 쓴 후 Sutter는 마침내 자신의 의견을 바꾸었고 더 이상 그러한 조언을 제공하지 않습니다. 이러한 생각의 변화는 2014년 CppCon 연설에서 확인할 수 있습니다. “기본으로 돌아가라! 현대 C++ 스타일의 필수 요소". 꼭 살펴보시고 다음 인터넷 링크로 넘어가겠습니다.

그리고 다음에는 21세기의 주요 프로그래밍 리소스인 StackOverflow가 있습니다. 아니면 오히려 이 글을 쓰는 시점에 긍정적인 반응 수가 1700을 초과하는 답변이었습니다. 질문은: 복사 및 교환 관용구가 무엇입니까? , 그리고 제목에서 알 수 있듯이 우리가 보고 있는 주제와는 전혀 다릅니다. 그러나 이 질문에 대한 대답에서 저자는 우리가 관심을 갖는 주제도 다루고 있습니다. 그는 또한 "어차피 인수가 복사된다면" PPZ를 사용하라고 조언합니다(이제 이에 대한 약어도 신이 소개할 시간입니다). 그리고 일반적으로 이 조언은 그의 답변과 거기에서 논의된 연산자의 틀 내에서 매우 적절해 보이지만 저자는 이 특별한 경우뿐만 아니라 더 넓은 방식으로 그러한 조언을 자유롭게 제공할 수 있습니다. 게다가 그는 우리가 이전에 논의한 모든 팁보다 더 나아가 C++03 코드에서도 이 작업을 수행할 것을 요구합니다! 저자가 그러한 결론을 내리게 된 계기는 무엇입니까?

분명히 답변의 저자는 다른 책 저자이자 Boost.MPL의 시간제 개발자인 Dave Abrahams의 기사에서 주요 영감을 얻었습니다. 기사 제목은 "속도를 원하십니까?" 가치를 전달하세요.” , 2009년 8월에 출판되었습니다. C++11이 채택되고 이동 의미론이 도입되기 2년 전입니다. 이전 사례와 마찬가지로 독자가 직접 기사를 읽는 것이 좋지만 Dave가 PPZ에 찬성하는 주요 주장(사실 단 하나의 주장만 있음)을 제시하겠습니다. PPZ를 사용해야 합니다. , PPSC에는 없는 "복사 건너뛰기" 최적화가 잘 작동하기 때문입니다(복사 제거). 기사에 대한 댓글을 읽어보면 그가 추진하는 조언이 보편적이지 않다는 것을 알 수 있는데, 이는 논평자들의 비판에 응답할 때 저자 자신이 확인하는 것입니다. 그러나 이 기사에는 인수가 복사될 경우 PPP를 사용하라는 명시적인 조언(지침)이 포함되어 있습니다. 그건 그렇고, 누군가 관심이 있다면 "속도를 원하십니까?"라는 기사를 읽을 수 있습니다. (항상) 가치를 전달하지 마세요.” . 제목에서 알 수 있듯이 이 글은 Dave의 글에 대한 답변이므로, 첫 번째 글을 읽으셨다면 이번 글도 꼭 읽어보세요!

불행히도 (일부에게는 다행스럽게도) 인기 사이트의 그러한 기사와 (더욱 그렇습니다) 인기있는 답변은 단순히 글쓰기가 덜 필요하고 오래된 교리가 더 이상 흔들리지 않기 때문에 모호한 기술 (사소한 예)을 대량으로 사용하게 만듭니다. 당신이 벽에 부딪히면 언제든지 “그 인기 있는 조언”을 참조할 수 있습니다. 이제 코드 작성에 대한 권장 사항을 제공하는 다양한 리소스를 숙지하시기 바랍니다.

왜냐하면 이제 다양한 표준과 권장 사항도 온라인에 게시되므로 이 섹션을 '네트워크 지혜'로 분류하기로 결정했습니다. 그래서 여기서 저는 두 가지 소스에 대해 이야기하고 싶습니다. 그 목적은 바로 이 코드를 작성하는 방법에 대한 팁(지침)을 제공하여 C++ 프로그래머의 코드를 더 좋게 만드는 것입니다.

제가 고려하고 싶은 첫 번째 규칙 세트는 제가 이 글을 쓰도록 강요한 마지막 지푸라기였습니다. 이 세트는 clang-tidy 유틸리티의 일부이며 외부에는 존재하지 않습니다. clang과 관련된 모든 것과 마찬가지로 이 유틸리티는 매우 인기가 높으며 이미 CLion 및 Resharper C++와 통합되었습니다(제가 그렇게 알게 되었습니다). 따라서 clang-tydy에는 PPSC를 통해 인수를 허용하는 생성자에서 작동하는 값별 현대화 규칙이 포함되어 있습니다. 이 규칙은 PPSC를 PPZ로 대체할 것을 제안합니다. 게다가 이 글을 쓰는 시점에 이 규칙에 대한 설명에는 이 규칙이 다음과 같다는 언급이 포함되어 있습니다. 안녕생성자에게만 적용되지만 생성자(그들은 누구입니까?)는 이 규칙을 다른 엔터티로 확장하는 사람들의 도움을 기꺼이 받아들입니다. 설명에는 Dave의 기사에 대한 링크도 있습니다. 다리가 어디에서 왔는지 분명합니다.

마지막으로, 다른 사람들의 지혜와 권위 있는 의견에 대한 이 리뷰를 마무리하기 위해 C++ 코드 작성에 대한 공식 지침인 C++ 핵심 지침을 살펴보는 것이 좋습니다. 주요 편집자는 Herb Sutter와 Bjarne Stroustrup입니다(나쁘지 않습니까?). 따라서 이러한 권장 사항에는 "in" 매개 변수의 경우 저렴하게 복사된 유형을 값으로 전달하고 다른 유형은 const를 참조하여 전달합니다.라는 규칙이 포함되어 있습니다. 이는 오래된 지혜인 모든 곳에 PPSK를 적용하고 작은 개체에 대해 PPP를 완전히 반복합니다. 이 팁에서는 고려해야 할 몇 가지 대안을 설명합니다. 인수 전달에 최적화가 필요한 경우. 그러나 PPZ는 대안 목록에 포함되지 않습니다!

주목할만한 다른 출처가 없기 때문에 두 가지 전송 방법에 대한 직접적인 분석으로 넘어갈 것을 제안합니다.

분석

앞의 전체 텍스트는 나에게는 다소 특이한 방식으로 작성되었습니다. 나는 다른 사람의 의견을 제시하고 심지어 내 의견을 표현하지 않으려고 노력합니다(결과가 좋지 않다는 것을 알고 있습니다). 주로 다른 사람들의 의견과 그에 대한 간략한 개요를 작성하는 것이 내 목표이기 때문에 다른 저자에게서 찾은 특정 주장에 대한 자세한 고려를 연기했습니다. 이 섹션에서는 권위를 언급하거나 의견을 제시하는 것이 아니라 PPSC와 PPZ의 객관적인 장단점을 살펴보고 주관적인 인식을 가미하겠습니다. 물론 앞서 논의한 내용 중 일부가 반복될 것이지만 아쉽게도 이것이 이 기사의 구조입니다.

PPP에 장점이 있나요?

따라서 찬반 주장을 고려하기 전에 가치 전달이 우리에게 제공하는 이점이 무엇이며 어떤 경우에 있는지 살펴볼 것을 제안합니다. 다음과 같은 클래스가 있다고 가정해 보겠습니다.

클래스 copymover (public : void setbyvaluer (accounter byvaluer) (m_byvaluer = std :: move (byvaluer);) void setbyrefer (const accounter & byrefer) (m_byrefer = byrefer;) void setbaluerandnotmover (accounter byvaluer) otmover;) void setRvaluer (계정&& rvaluer) ( m_Rvaluer = std::move(rvaluer); ) );

이 기사의 목적상 처음 두 함수에만 관심이 있지만 대조용으로 사용하기 위해 네 가지 옵션을 포함했습니다.

Accounter 클래스는 복사/이동 횟수를 계산하는 간단한 클래스입니다. CopyMover 클래스에는 다음 옵션을 고려할 수 있는 기능이 구현되어 있습니다.

    움직이는인수를 통과했습니다.

    값으로 전달한 다음 사자인수를 통과했습니다.

이제 다음과 같이 각 함수에 lvalue를 전달하면 다음과 같습니다.

회계사 byRefer; 회계사 byValuer; ValuerAndNotMover의 회계사; CopyMover copyMover; copyMover.setByRefer(byRefer); copyMover.setByValuer(byValuer); copyMover.setByValuerAndNotMover(byValuerAndNotMover);

그러면 다음과 같은 결과를 얻습니다.

확실한 승자는 PPSC입니다. 왜냐하면... PPZ는 하나의 사본만 제공하는 반면 PPZ는 하나의 사본과 하나의 이동을 제공합니다.

이제 rvalue를 전달해 보겠습니다.

CopyMover copyMover; copyMover.setByRefer(계정()); copyMover.setByValuer(계정()); copyMover.setByValuerAndNotMover(계정()); copyMover.setRvaluer(Accounter());

우리는 다음을 얻습니다:

여기에는 확실한 승자가 없습니다. 왜냐하면... PPZ와 PPSK 모두 하나의 작업을 수행하지만 PPZ는 이동을 사용하고 PPSK는 복사를 사용하므로 PPZ에 승리를 줄 수 있습니다.

하지만 우리의 실험은 여기서 끝나지 않습니다. 다음 함수를 추가하여 간접 호출(후속 인수 전달 포함)을 시뮬레이션해 보겠습니다.

Void setByValuer(Accounter byValuer, CopyMover& copyMover) ( copyMover.setByValuer(std::move(byValuer)); ) void setByRefer(const Accounter& byRefer, CopyMover& copyMover) ( copyMover.setByRefer(byRefer); ) ...

우리는 그것들을 사용하지 않았을 때와 똑같은 방식으로 그것들을 사용할 것이므로 코드를 반복하지 않을 것입니다(필요한 경우 저장소를 살펴보십시오). 따라서 lvalue의 경우 결과는 다음과 같습니다.

PPSC는 PPZ와의 간격을 늘려 단일 복사본으로 유지되는 반면 PPZ에는 이미 3개의 작업(1개 더 이동)이 있습니다!

이제 rvalue를 전달하고 다음 결과를 얻습니다.

이제 PPZ에는 2개의 동작이 있고 PPSC에는 여전히 하나의 복사본이 있습니다. 이제 PPZ를 우승자로 지명할 수 있나요? 아니, 왜냐면 한 번의 움직임이 적어도 한 번의 복사본보다 나쁘지 않아야 한다면 2번의 움직임에 대해서는 동일하다고 말할 수 없습니다. 따라서 이 예에서는 승자가 없습니다.

그들은 나에게 이의를 제기할 수도 있습니다. “작가님, 당신은 편향된 의견을 가지고 있고 당신에게 유익한 것을 끌어들이고 있습니다. 2개만 움직여도 복사하는 것보다 저렴해요!” 나는 이 말에 동의할 수 없다 전체적으로, 왜냐하면 이동이 복사보다 얼마나 빠른지는 클래스에 따라 다르지만 "저렴한" 이동에 대해서는 별도의 섹션에서 살펴보겠습니다.

여기서 우리는 흥미로운 점을 다루었습니다. 하나의 간접 호출을 추가했고 PPP는 "가중치"에 정확히 하나의 작업을 추가했습니다. 간접 통화가 많을수록 PPZ를 사용할 때 더 많은 작업이 수행되고 PPSC의 경우 번호는 변경되지 않는다는 것을 이해하기 위해 MSTU 졸업장을 가질 필요는 없다고 생각합니다.

위에서 논의된 모든 것은 누구에게도 드러날 것 같지 않았으며 우리는 심지어 실험을 수행하지 않았을 수도 있습니다. 이 모든 숫자는 대부분의 C++ 프로그래머에게 첫눈에 분명할 것입니다. 사실, 한 가지 점은 여전히 ​​명확하게 설명할 가치가 있습니다. 왜 rvalue의 경우 PZ에 복사본(또는 다른 이동)이 없고 단 하나의 이동만 있는지입니다.

자, 복사 횟수와 이동 횟수를 직접 관찰하여 PPZ와 PPSC 간의 전송 차이를 살펴보았습니다. 이러한 간단한 예에서도 PPSC에 비해 PPZ의 장점은 다음과 같습니다. 아니다분명히, 저는 여전히 약간 가식적으로 다음과 같은 결론을 내리고 있습니다. 함수 인수를 계속 복사할 예정이라면 인수를 값으로 함수에 전달하는 것을 고려하는 것이 합리적입니다. 내가 왜 이런 결론을 내리게 되었는가? 다음 섹션으로 원활하게 넘어갈 수 있도록 합니다.

복사해 보자면...

그래서 우리는 속담 "if"에 도달합니다. 우리가 접한 대부분의 주장은 PPSC 대신 PPP의 보편적 구현을 ​​요구하지 않았으며 "인수가 어쨌든 복사되는 경우"에만 그렇게 할 것을 요구했습니다. 이제 이 주장의 문제점이 무엇인지 알아낼 때입니다.

코드 작성 방법에 대한 간단한 설명부터 시작하고 싶습니다. 최근 내 코딩 프로세스는 점점 TDD와 비슷해졌습니다. 모든 클래스 메서드 작성은 이 메서드가 나타나는 테스트를 작성하는 것부터 시작됩니다. 따라서 테스트 작성을 시작할 때, 테스트 작성 후 메소드를 생성할 때 인수를 복사할지 여부는 아직 알 수 없습니다. 물론 모든 함수가 이런 방식으로 생성되는 것은 아니며, 테스트를 작성하는 과정에서도 어떤 구현이 나올지 정확히 알 수 있는 경우가 많습니다. 그러나 이것이 항상 일어나는 것은 아닙니다!

누군가는 메서드가 원래 어떻게 작성되었는지는 중요하지 않으며, 메서드가 형태를 갖추고 거기에서 무슨 일이 일어나고 있는지가 완전히 명확해지면 인수를 전달하는 방법을 변경할 수 있다고 나에게 반대할 수 있습니다. 아니다 ). 나는 이것에 부분적으로 동의합니다. 실제로 이런 식으로 할 수 있지만 이것은 구현이 변경되었기 때문에 인터페이스를 변경해야 하는 일종의 이상한 게임과 관련이 있습니다. 그러면 다음 딜레마가 발생합니다.

구현 방법에 따라 인터페이스를 수정(또는 계획)하는 것으로 나타났습니다. 나는 나 자신을 OOP 및 소프트웨어 아키텍처의 기타 이론적 계산 전문가라고 생각하지 않지만 구현이 인터페이스에 영향을 주어서는 안되는 기본 규칙에 분명히 위배됩니다. 물론 특정 구현 세부 사항(언어의 기능이든 대상 플랫폼이든)은 여전히 ​​어떤 방식으로든 인터페이스를 통해 유출되지만, 그러한 항목의 수를 늘리는 것이 아니라 줄이도록 노력해야 합니다.

글쎄, 신의 축복이 있기를 바랍니다. 이 경로를 따라가면서 인수 복사 측면에서 구현에서 수행하는 작업에 따라 인터페이스를 계속 변경하겠습니다. 이 메서드를 작성했다고 가정해 보겠습니다.

Void setName(이름 이름) ( m_Name = move(이름); )

변경 사항을 저장소에 커밋했습니다. 시간이 지남에 따라 우리 소프트웨어 제품은 새로운 기능을 획득하고 새로운 프레임워크가 통합되었으며 수업의 변화를 외부 세계에 알리는 임무가 생겼습니다. 저것들. 우리는 메소드에 몇 가지 알림 메커니즘을 추가하여 Qt 신호와 유사하게 만들 것입니다.

Void setName(이름 이름) ( m_Name = move(name); nameChanged(m_Name) 내보내기; )

이 코드에 문제가 있나요? 먹다. setName을 호출할 때마다 신호를 보냅니다. 의미 m_Name이 변경되지 않았습니다. 성능 문제 외에도 위 알림을 수신하는 코드가 어떻게든 setName 을 호출하기 때문에 이 상황은 무한 루프로 이어질 수 있습니다. 이러한 모든 문제를 피하기 위해 이러한 방법은 가장 자주 다음과 같습니다.

Void setName(이름 이름) ( if(name == m_Name) return; m_Name = move(name); 내보내기 nameChanged(m_Name); )

위에서 설명한 문제를 제거했지만 이제 "어쨌든 복사하면..." 규칙이 실패했습니다. 더 이상 인수를 무조건 복사할 수 없으며, 이제 인수가 변경될 경우에만 복사합니다! 그럼 이제 우리는 무엇을 해야 할까요? 인터페이스를 바꾸시겠습니까? 좋아요, 이번 수정으로 인해 클래스 인터페이스를 변경해 보겠습니다. 우리 클래스가 추상 인터페이스에서 이 메서드를 상속했다면 어떻게 될까요? 거기도 바꿔보자! 구현이 바뀌어서 변화가 많이 생기나요?

다시 그들은 저에게 이의를 제기할 수 있습니다. 저자는 이 조건이 해결될 때 왜 성냥 비용을 절약하려고 합니까? 예, 대부분의 전화는 거짓입니다! 이것에 대한 확신이 있습니까? 어디? 그리고 내가 성냥을 절약하기로 결정했다면 우리가 PPZ를 사용했다는 사실이 바로 그러한 절약의 결과가 아니었을까요? 나는 효율성을 내세우는 당 노선을 계속 이어갈 뿐이다.

생성자

특히 clang-tidy에는 다른 메서드/함수에서는 아직 작동하지 않는 생성자에 대한 특별한 규칙이 있으므로 생성자에 대해 간략하게 살펴보겠습니다. 다음과 같은 클래스가 있다고 가정해 보겠습니다.

클래스 JustClass ( 공개: JustClass(const string& justString): m_JustString(justString) ( ) 비공개: 문자열 m_JustString; );

분명히 매개변수가 복사되고 clang-tidy는 생성자를 다음과 같이 다시 작성하는 것이 좋을 것이라고 알려줍니다.

JustClass(string justString): m_JustString(move(justString)) ( )

그리고 솔직히 말해서 여기서 논쟁하기는 어렵습니다. 결국 우리는 실제로 항상 복사합니다. 그리고 대부분의 경우 생성자를 통해 무언가를 전달할 때 이를 복사합니다. 그러나 더 자주가 항상을 의미하는 것은 아닙니다. 또 다른 예는 다음과 같습니다.

클래스 TimeSpan ( 공개: TimeSpan(DateTime start, DateTime end) ( if(start > end) throw InvalidTimeSpan(); m_Start = move(start); m_End = move(end); ) private: DateTime m_Start; DateTime m_End; );

여기서는 항상 복사하는 것이 아니라 날짜가 올바르게 표시되는 경우에만 복사합니다. 물론 대부분의 경우에 그러할 것이다. 하지만 항상 그런 것은 아니다.

다른 예를 들 수도 있지만 이번에는 코드가 없습니다. 큰 개체를 허용하는 클래스가 있다고 상상해 보세요. 이 클래스는 오랫동안 존재해 왔으며 이제 구현을 업데이트할 차례입니다. 우리는 대규모 시설의 절반 이상이 필요하지 않으며(수년에 걸쳐 성장함) 그보다 더 적을 수도 있다는 것을 알고 있습니다. 값을 전달하여 이에 대해 뭔가를 할 수 있나요? 아니요. 복사본이 계속 생성되므로 아무것도 할 수 없습니다. 하지만 PPSC를 사용한다면 우리가 하는 일을 간단히 바꿀 수 있을 것입니다. 내부에디자이너. 그리고 이것이 핵심 포인트입니다. PPSC를 사용하면 함수(생성자) 구현에서 발생하는 작업과 시기를 제어하지만 PPZ를 사용하면 복사에 대한 제어를 잃게 됩니다.

이 섹션에서 무엇을 얻을 수 있나요? "어차피 복사해도..."라는 주장은 매우 논란의 여지가 많습니다. 우리는 무엇을 복사할지 항상 알 수 없으며, 알더라도 이것이 앞으로도 계속될지 확신할 수 없는 경우가 많습니다.

이사비용이 저렴해요

움직임의 의미론이 나타난 바로 그 순간부터 그것은 현대 C++ 코드가 작성되는 방식에 심각한 영향을 미치기 시작했으며 시간이 지남에 따라 이 영향은 더욱 강해졌습니다. 값이 싼복사하는 것과 비교됩니다. 하지만 그렇습니까? 움직임이 있다는 것이 사실인가요? 언제나저렴한 수술? 이것이 우리가 이 섹션에서 알아내려고 노력할 것입니다.

바이너리 대형 객체

간단한 예부터 시작하겠습니다. 다음과 같은 클래스가 있다고 가정해 보겠습니다.

구조체 블롭( std::array 데이터; );

평범한 얼룩(BDO, 영문 BLOB) 등 다양한 상황에서 사용할 수 있습니다. 참조와 값으로 전달하는 데 드는 비용을 살펴보겠습니다. 우리의 BDO는 다음과 같이 사용됩니다:

Void Storage::setBlobByRef(const Blob& blob) ( m_Blob = blob; ) void Storage::setBlobByVal(Blob blob) ( m_Blob = move(blob); )

그리고 우리는 이러한 함수를 다음과 같이 호출할 것입니다:

Const Blob blob(); 저장; Storage.setBlobByRef(blob); Storage.setBlobByVal(blob);

다른 예제의 코드는 이름과 유형만 다를 뿐 이 코드와 동일하므로 나머지 예제에 대해서는 설명하지 않겠습니다. 모든 것이 저장소에 있습니다.

측정을 진행하기 전에 결과를 예측해 보겠습니다. 따라서 Storage 클래스 객체에 저장하려는 4KB std::array가 있습니다. 앞서 알아낸 것처럼 PPSC의 경우 복사본이 하나 있고 PPZ의 경우 복사본 하나와 이동 하나가 있습니다. 어레이를 이동할 수 없다는 점을 고려하여 PPZ에는 2개의 복사본이 있고 PPSC에는 1개의 복사본이 있습니다. 저것들. PPSC의 성능은 두 배나 뛰어날 것으로 예상됩니다.

이제 테스트 결과를 살펴보겠습니다.

이 테스트와 모든 후속 테스트는 MSVS 2017(15.7.2) 및 /O2 플래그를 사용하여 동일한 컴퓨터에서 실행되었습니다.

연습은 가정과 일치했습니다. 값 전달은 배열의 경우 이동이 복사와 완전히 동일하기 때문에 2배 더 비쌉니다.

또 다른 예인 일반 std::string 을 살펴보겠습니다. 우리는 무엇을 기대할 수 있습니까? 우리는 현대 구현이 두 가지 유형의 문자열, 즉 짧은 문자열(약 16자)과 긴 문자열(짧은 문자열보다 긴 문자열)을 구별한다는 것을 알고 있습니다(이 기사에서 이에 대해 논의했습니다). 짧은 버퍼의 경우 char 의 일반 C 배열인 내부 버퍼가 사용되지만 긴 버퍼는 이미 힙에 배치됩니다. 우리는 짧은 줄에는 관심이 없습니다. 왜냐하면... 결과는 BDO와 동일하므로 긴 줄에 집중하겠습니다.

따라서 긴 문자열이 있으면 문자열을 이동하는 것이 매우 저렴해야 한다는 것이 분명합니다(포인터만 이동). 따라서 문자열을 이동해도 결과에 전혀 영향을 미치지 않으며 PPZ가 결과를 제공해야 한다는 사실을 믿을 수 있습니다. PPSC보다 나쁘지 않습니다. 실제로 확인해 보고 다음과 같은 결과를 얻으세요.

이 "현상"에 대해 설명하겠습니다. 그렇다면 기존 문자열을 이미 존재하는 문자열에 복사하면 어떻게 될까요? 간단한 예를 살펴보겠습니다.

문자열 우선(64, "C"); 문자열 초(64, "N"); //... 두 번째 = 첫 번째;

64자 문자열이 두 개 있으므로 이를 생성할 때 내부 버퍼가 부족하여 두 문자열이 모두 힙에 할당됩니다. 이제 첫 번째에서 두 번째로 복사합니다. 왜냐하면 행 크기는 동일합니다. 분명히 첫 번째의 모든 데이터를 수용할 수 있도록 두 번째에 할당된 공간이 충분하므로 두 번째 = 첫 번째입니다. 평범한 memcpy가 될 것입니다. 하지만 약간 수정된 예를 보면 다음과 같습니다.

문자열 우선(64, "C"); 문자열 두 번째 = 첫 번째;

그러면 더 이상 Operator= 가 호출되지 않지만 복사 생성자가 호출됩니다. 왜냐하면 생성자를 다루고 있으므로 생성자에는 기존 메모리가 없습니다. 먼저 선택한 다음 먼저 복사해야 합니다. 저것들. 이것은 메모리 할당이고 그 다음은 memcpy 입니다. 여러분과 제가 알고 있듯이 전역 힙에 메모리를 할당하는 것은 일반적으로 비용이 많이 드는 작업이므로 두 번째 예에서 복사하는 것이 첫 번째 예에서 복사하는 것보다 비용이 더 많이 듭니다. 힙 메모리 할당당 비용이 더 높습니다.

이것이 우리 주제와 어떤 관련이 있습니까? 가장 직접적인 방법은 첫 번째 예는 PPSC에서 발생하는 일을 정확하게 보여주고 두 번째 예에서는 PPZ에서 발생하는 일을 보여주기 때문입니다. PPZ의 경우 항상 새 행이 생성되고 PPSC의 경우 기존 행이 재사용됩니다. 실행 시간의 차이를 이미 확인했으므로 여기에 추가할 내용은 없습니다.

여기서 우리는 PPP를 사용할 때 상황에 맞지 않게 작업하므로 PPP가 제공할 수 있는 모든 이점을 사용할 수 없다는 사실에 다시 직면하게 됩니다. 그리고 이전에 우리가 이론적 미래 변화 측면에서 추론했다면 여기서는 생산성의 매우 구체적인 실패를 관찰하고 있습니다.

물론 누군가는 문자열이 서로 다르다는 점에 반대할 수 있으며 대부분의 유형은 그런 식으로 작동하지 않습니다. 이에 대해 나는 다음과 같이 대답할 수 있습니다. 앞에서 설명한 모든 내용은 요소 팩에 대해 즉시 힙에 메모리를 할당하는 모든 컨테이너에 적용됩니다. 또한 다른 유형에서 사용되는 상황에 맞는 최적화가 무엇인지 누가 알겠습니까?

이 섹션에서 무엇을 빼내야 합니까? 이동이 정말 저렴하다고 해서 복사를 복사+이동으로 대체한다고 해서 항상 성능면에서 비교할만한 결과가 나오는 것은 아닙니다.

복합형

마지막으로 여러 객체로 구성되는 유형을 살펴보겠습니다. 이것을 개인 고유의 데이터로 구성된 Person 클래스라고 하겠습니다. 일반적으로 이는 이름, 성, 우편번호 등입니다. 이 모든 것을 문자열로 표현할 수 있으며 Person 클래스의 필드에 입력하는 문자열이 짧을 가능성이 있다고 가정합니다. 실생활에서는 짧은 줄을 측정하는 것이 가장 유용할 것이라고 생각하지만, 더 완전한 그림을 제공하기 위해 다양한 크기의 줄을 살펴볼 것입니다.

또한 10개의 필드가 있는 Person을 사용할 것이지만 이를 위해 클래스 본문에 직접 10개의 필드를 만들지는 않을 것입니다. Person의 구현은 그 깊이에 컨테이너를 숨깁니다. 이렇게 하면 Person이 실제 클래스인 경우 작동 방식에서 실질적으로 벗어나지 않고 테스트 매개 변수를 변경하는 것이 더 편리해집니다. 그러나 구현이 가능하며 언제든지 코드를 확인하고 내가 뭔가 잘못했는지 알려줄 수 있습니다.

이제 PPSC 및 PPZ를 사용하여 Storage로 전송하는 string 유형의 10개 필드가 있는 Person을 살펴보겠습니다.

보시다시피, 성능 면에서 큰 차이가 있습니다. 이는 이전 섹션 이후 독자들에게 놀라운 일이 아닙니다. 나는 또한 Person 클래스가 그러한 결과가 추상적인 것으로 무시되지 않을 만큼 충분히 "실제"라고 믿습니다.

그런데 이 글을 준비하면서 또 다른 예제를 준비했습니다: 여러 std::function 객체를 사용하는 클래스입니다. 내 생각으로는 PPZ에 비해 PPSC의 성능에서도 이점을 보여줄 것으로 예상되었지만 결과는 정반대였습니다! 하지만 여기서는 결과가 마음에 들지 않아서가 아니라 왜 그런 결과를 얻었는지 알아낼 시간이 없었기 때문에 여기서이 예를 제시하지 않습니다. 그럼에도 불구하고 저장소(프린터)에는 코드가 있고 테스트도 있습니다. 누구든지 알아내고 싶다면 연구 결과를 듣고 기뻐할 것입니다. 나중에 이 사례로 돌아갈 계획이며, 아무도 이 결과를 나보다 먼저 게시하지 않으면 별도의 기사에서 이를 고려할 것입니다.

결과

그래서 우리는 값으로 전달하는 것과 상수로 참조로 전달하는 것의 다양한 장단점을 살펴보았습니다. 우리는 몇 가지 예를 살펴보고 이 예에서 두 방법의 성능을 살펴보았습니다. 물론 이 기사는 완전할 수 없고 완전하지도 않지만, 제 생각에는 어떤 방법을 사용하는 것이 가장 좋은지에 대해 독립적이고 정보에 입각한 결정을 내리는 데 충분한 정보가 포함되어 있습니다. 누군가는 "왜 한 가지 방법을 사용하는가? 작업부터 시작하자!"라고 반대할 수 있습니다. 나는 이 논제에 전반적으로 동의하지만, 이번 상황에서는 동의하지 않습니다. 나는 언어에서 논쟁을 전달하는 방법은 오직 한 가지뿐이라고 믿습니다. 기본적으로 사용되는.

기본값은 무엇을 의미합니까? 이는 함수를 작성할 때 인수를 어떻게 전달해야 할지 생각하지 않고 단지 "기본값"을 사용한다는 의미입니다. C++ 언어는 많은 사람들이 기피하는 다소 복잡한 언어입니다. 그리고 제 생각에는 복잡성은 언어에 존재하는 언어 구조의 복잡성(일반적인 프로그래머는 결코 접하지 못할 수도 있음) 때문이 아니라 언어가 많은 생각을 하게 한다는 사실에 의해 발생합니다. 메모리를 늘리려면 여기서 이 기능을 사용하는 데 비용이 많이 드나요?

많은 프로그래머(C, C++ 등)는 2011년 이후 등장하기 시작한 C++에 대해 불신과 두려움을 갖고 있습니다. 나는 언어가 점점 더 복잡해지고 있고, 이제는 "전문가"만이 언어로 글을 쓸 수 있다는 등의 비판을 많이 들었습니다. 개인적으로 나는 이것이 그렇지 않다고 생각합니다. 반대로 위원회는 언어를 초보자에게 더 친숙하게 만드는 데 많은 시간을 할애하여 프로그래머가 언어의 기능에 대해 덜 생각할 필요가 있도록 합니다. 결국, 언어 문제로 어려움을 겪을 필요가 없다면 작업에 대해 생각할 시간이 있는 것입니다. 이러한 단순화에는 스마트 포인터, 람다 함수 등 언어에 나타나는 많은 기능이 포함됩니다. 동시에, 이제 우리가 더 공부해야 한다는 사실을 부정하지는 않지만, 공부하는 것이 무엇이 문제인가? 아니면 배워야 할 다른 인기 언어에는 변화가 일어나지 않습니까?

더욱이, “생각하고 싶지 않소? 그런 다음 PHP로 작성해 보세요.” 나는 그런 사람들에게 대답하고 싶지도 않습니다. 게임 현실의 예를 들어 보겠습니다. 스타크래프트의 첫 번째 부분에서 건물에 새로운 작업자가 생성되면 그가 광물(또는 가스) 추출을 시작하려면 수동으로 그곳으로 보내야 했습니다. 더욱이, 각 광물 팩에는 한계가 있어서, 한계에 도달하면 일꾼을 늘려도 소용이 없고, 심지어 서로 간섭하여 생산을 악화시킬 수도 있습니다. 이는 스타크래프트 2에서 변경되었습니다. 작업자는 자동으로 광물(또는 가스)을 채굴하기 시작하며, 현재 채굴 중인 작업자 수와 이 매장량의 한도도 표시됩니다. 이를 통해 플레이어와 기지의 상호 작용이 크게 단순화되어 기지 건설, 군대 축적, 적을 파괴하는 등 게임의 더 중요한 측면에 집중할 수 있습니다. 이것은 단지 대단한 혁신인 것처럼 보이지만 인터넷에서 시작된 것입니다! 사람들(그들은 누구인가?) 은 게임이 "망쳐지고 있다", "그들이 스타크래프트를 죽였다"고 소리치기 시작했습니다. 분명히 그러한 메시지는 일부 "엘리트" 클럽에 있기를 좋아하는 "비밀 지식의 수호자"와 "높은 APM의 숙련자"에게서만 나올 수 있습니다.

다시 본론으로 돌아가서, 코드 작성 방법에 대해 생각할 필요가 줄어들수록 당면한 문제 해결에 대해 생각하는 데 더 많은 시간이 필요합니다. PPSC 또는 PPZ 중 어떤 방법을 사용해야 하는지 생각한다고 해서 문제 해결에 조금도 가까워지지는 않습니다. 그래서 저는 단순히 그러한 것에 대한 생각을 거부하고 상수에 대한 참조를 전달하는 한 가지 옵션을 선택합니다. 왜? 일반적인 경우에는 PPP의 장점이 보이지 않고 특별한 경우는 별도로 고려해야 하기 때문입니다.

이것은 특별한 경우입니다. 어떤 방법으로든 PPSC가 병목 현상을 일으키는 것으로 밝혀지고 전송을 PPZ로 변경하면 성능이 크게 향상된다는 점을 알게 되었기 때문에 주저하지 않고 다음을 사용합니다. PPZ. 하지만 기본적으로 일반 함수와 생성자 모두에서 PPSC를 사용하겠습니다. 그리고 가능하다면 가능한 한 이 특정 방법을 홍보하겠습니다. 왜? PPP를 홍보하는 관행은 프로그래머의 대부분이 지식이 부족하고(원칙적으로 또는 아직 상황에 참여하지 않았음) 단순히 조언을 따른다는 사실 때문에 악의적이라고 생각하기 때문입니다. 게다가 여러 가지 상충되는 조언이 있는 경우 더 간단한 조언을 선택하고 이는 단순히 누군가 어딘가에서 뭔가를 들었다는 이유만으로 코드에 비관론을 불러일으키게 됩니다. 아, 그렇습니다. 이 사람은 자신이 옳다는 것을 증명하기 위해 Abrahams의 기사에 대한 링크를 제공할 수도 있습니다. 그런 다음 앉아서 코드를 읽고 다음과 같이 생각합니다. 이것을 작성한 프로그래머가 Java에서 왔기 때문에 매개변수가 값으로 전달된다는 사실인가요? 단지 "똑똑한" 기사를 많이 읽었을 뿐인지, 아니면 실제로 기술 사양?

PPSC는 읽기가 훨씬 쉽습니다. 그 사람은 C++의 "좋은 형식"을 분명히 알고 있고 계속 진행합니다. 시선은 머뭇거리지 않습니다. PPSC를 사용하는 방법은 수년 동안 C++ 프로그래머들에게 가르쳐졌습니다. 이를 포기하는 이유는 무엇입니까? 이는 또 다른 결론으로 ​​이어집니다. 메소드 인터페이스가 PPP를 사용하는 경우, 그 이유에 대한 설명도 있어야 합니다. 다른 경우에는 PPSC를 적용해야 합니다. 물론 예외 유형이 있지만 string_view , 초기화_list , 다양한 반복자 등이 암시되어 있기 때문에 여기서는 언급하지 않습니다. 그러나 이는 예외이며 프로젝트에서 사용되는 유형에 따라 목록이 확장될 수 있습니다. 그러나 본질은 C++98 이후로 동일하게 유지됩니다. 기본적으로 우리는 항상 PPCS를 사용합니다.

std::string의 경우 작은 문자열에서는 차이가 없을 가능성이 높습니다. 이에 대해서는 나중에 설명하겠습니다.

"포인트 배치"에 대한 가식적인 주석에 대해 미리 사과드립니다. 하지만 어떻게든 여러분을 기사로 유인해야 합니다.)) 제 입장에서는 초록이 여전히 여러분의 기대에 부응할 수 있도록 노력하겠습니다.

우리가 말하는 내용을 간략하게

모두가 이미 알고 있지만 처음에는 1C에서 메서드 매개 변수를 전달하는 방법을 상기시켜 드리겠습니다. "참조로" 또는 "값으로" 전달할 수 있습니다. 첫 번째 경우에는 호출 시점과 동일한 값을 메서드에 전달하고 두 번째 경우에는 해당 값의 복사본을 전달합니다.

기본적으로 1C에서는 인수가 참조로 전달되며 메서드 내부의 매개 변수에 대한 변경 사항은 메서드 외부에서 볼 수 있습니다. 여기서 질문에 대한 추가 이해는 "매개변수 변경"이라는 단어로 정확히 무엇을 이해하는지에 따라 달라집니다. 따라서 이는 재할당을 의미할 뿐 그 이상은 아닙니다. 또한 할당은 암시적일 수 있습니다(예: 출력 매개 변수에서 무언가를 반환하는 플랫폼 메서드 호출).

그러나 매개변수가 참조로 전달되는 것을 원하지 않으면 매개변수 앞에 키워드를 지정할 수 있습니다. 의미

프로시저 ByValue(값 매개변수) 매개변수 = 2; EndProcedure 매개변수 = 1; ByValue(매개변수); 보고서(매개변수); // 1을 인쇄합니다.

모든 것이 약속대로 작동합니다. 매개변수 값을 변경(또는 "교체")해도 메서드 외부의 값은 변경되지 않습니다.

글쎄, 농담이 뭐야?

흥미로운 순간은 기본 유형(문자열, 숫자, 날짜 등)이 아닌 객체를 매개변수로 전달하기 시작할 때 시작됩니다. 여기에서 객체의 "얕은" 복사본과 "깊은" 복사본과 포인터(C++ 용어가 아닌 추상 핸들)와 같은 개념이 적용됩니다.

참조로 개체(예: 값 테이블)를 전달할 때 플랫폼 메모리에 개체를 "보유"하는 포인터 값 자체(특정 핸들)를 전달합니다. 값으로 전달되면 플랫폼은 이 포인터의 복사본을 만듭니다.

즉, 참조로 개체를 전달하는 경우 메서드에서 "Array" 값을 매개 변수에 할당하면 호출 시점에 배열을 받게 됩니다. 참조로 전달된 값의 재할당은 호출 위치에서 볼 수 있습니다.

프로시저 ProcessValue(매개변수) 매개변수 = 새 배열; EndProcedure 테이블 = 새 ValueTable; 프로세스값(테이블); Report(값 유형(테이블)); // 배열을 출력합니다

객체를 값으로 전달하면 호출 시점에 값 테이블이 손실되지 않습니다.

객체 내용 및 상태

값으로 전달하는 경우 전체 개체가 복사되지 않고 해당 포인터만 복사됩니다. 객체 인스턴스는 동일하게 유지됩니다. 참조 또는 값으로 객체를 전달하는 방법은 중요하지 않습니다. 값 테이블을 지우면 테이블 자체도 지워집니다. 이 청소는 어디에서나 볼 수 있습니다. 왜냐하면... 객체는 하나뿐이었고 그것이 메소드에 얼마나 정확하게 전달되었는지는 중요하지 않았습니다.

프로시저 ProcessValue(Parameter) Parameter.Clear(); EndProcedure 테이블 = 새 ValueTable; 테이블.추가(); 프로세스값(테이블); Report(Table.Quantity()); // 0을 출력합니다

개체를 메서드에 전달할 때 플랫폼은 포인터(C++의 직접적인 아날로그가 아닌 조건부)를 사용하여 작동합니다. 객체가 참조로 전달되면 객체가 있는 1C 가상 머신의 메모리 셀을 다른 객체로 덮어쓸 수 있습니다. 객체가 값으로 전달되면 포인터가 복사되고 객체를 덮어써도 메모리 위치를 원래 객체로 덮어쓰지 않습니다.

동시에 어떤 변화라도 상태객체(정리, 속성 추가 등)는 객체 자체를 변경하며 객체가 전송된 방법 및 위치와는 전혀 관련이 없습니다. 객체 인스턴스의 상태가 변경되었습니다. 이에 대한 "참조"와 "값"이 많이 있을 수 있지만 인스턴스는 항상 동일합니다. 객체를 메소드에 전달한다고 해서 전체 객체의 복사본이 생성되는 것은 아닙니다.

그리고 이것은 항상 사실이지만...

클라이언트-서버 상호 작용

플랫폼은 서버 호출을 매우 투명하게 구현합니다. 간단히 메소드를 호출하면 플랫폼이 메소드의 모든 매개변수를 직렬화(문자열로 변환)하고 이를 서버에 전달한 다음 출력 매개변수를 클라이언트에 다시 반환합니다. 여기서 해당 매개변수는 역직렬화되어 다음과 같이 실행됩니다. 서버에 가본 적이 없다면.

아시다시피 모든 플랫폼 개체가 직렬화 가능한 것은 아닙니다. 여기서 제한이 커집니다. 모든 개체가 클라이언트에서 서버 메서드로 전달될 수 있는 것은 아닙니다. 직렬화할 수 없는 객체를 전달하면 플랫폼은 나쁜 단어를 사용하기 시작합니다.

  • 프로그래머의 의도를 명시적으로 선언합니다. 메서드 시그니처를 보면 어떤 매개변수가 입력되고 어떤 매개변수가 출력되는지 명확하게 알 수 있습니다. 이 코드는 읽고 유지하기가 더 쉽습니다.
  • 서버의 "참조별" 매개변수 변경 사항이 클라이언트의 호출 지점에 표시되도록 하려면, p기사 시작 부분에 설명된 동작을 보장하기 위해 플랫폼 자체는 반드시 클라이언트에 대한 링크를 통해 서버에 전달된 매개변수를 반환합니다. 매개변수를 반환할 필요가 없으면 트래픽 오버런이 발생합니다. 데이터 교환을 최적화하려면 출력에서 ​​필요하지 않은 값을 가진 매개변수에 Value라는 단어를 표시해야 합니다.

여기서 두 번째 점은 주목할 만하다. 트래픽을 최적화하기 위해 매개변수에 Value라는 단어가 표시된 경우 플랫폼은 매개변수 값을 클라이언트에 반환하지 않습니다. 이것은 모두 훌륭하지만 흥미로운 효과로 이어집니다.

이미 말했듯이 객체가 서버로 전송되면 직렬화가 발생합니다. 개체의 "깊은" 복사본이 수행됩니다. 그리고 한마디가 있다면 의미개체는 서버에서 클라이언트로 다시 이동하지 않습니다. 이 두 가지 사실을 추가하면 다음과 같은 결과를 얻습니다.

&OnServerProcedureByLink(매개변수) Parameter.Clear(); EndProcedure &OnServerProcedureByValue(값 매개변수) Parameter.Clear(); EndProcedure &OnClient 프로시저 ByValueClient(값 매개변수) Parameter.Clear(); EndProcedure OnClient 프로시저 CheckValue() List1= 새 ListValues; List1.Add("안녕하세요"); 목록2 = 목록1.복사(); 목록3 = 목록1.복사(); // 객체가 완전히 복사되고, // 서버로 전송된 후 반환됩니다. // 목록 지우기는 호출 지점에서 볼 수 있습니다. ByRef(List1); // 객체가 완전히 복사되고, // 서버로 전송됩니다. 그것은 돌아오지 않습니다. // ByValue(List2) 호출 시점에는 목록 지우기가 표시되지 않습니다. // 객체 포인터만 복사됩니다. // 목록 지우기는 ByValueClient(List3) 호출 시점에 표시됩니다. Report(List1.Quantity()); Report(List2.Quantity()); Report(List3.Quantity()); 절차 종료

요약

간단히 말해서 다음과 같이 요약할 수 있습니다.

  • 참조로 전달하면 객체를 완전히 다른 객체로 "덮어쓸" 수 있습니다.
  • 값을 전달하면 객체를 "덮어쓰기"할 수 없지만 객체 내부 상태의 변경 사항이 표시됩니다. 우리는 동일한 객체 인스턴스로 작업하고 있습니다
  • 서버 호출을 할 때 작업은 개체의 다른 인스턴스로 수행됩니다. 깊은 복사가 수행되었습니다. 예어 의미서버 인스턴스가 클라이언트 인스턴스로 다시 복사되는 것을 방지하고 서버에 있는 개체의 내부 상태를 변경해도 클라이언트에 유사한 변경이 발생하지 않습니다.

이 간단한 규칙 목록을 통해 "값 기준" 및 "참조 기준" 매개 변수 전달과 관련하여 동료와의 분쟁을 더 쉽게 해결할 수 있기를 바랍니다.