복사 초기화와 직접 초기화간에 차이가 있습니까?
이 기능이 있다고 가정하십시오.
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
각 그룹에서 이러한 진술은 동일합니까? 아니면 일부 초기화에 여분의 (최적화 가능한) 사본이 있습니까?
사람들이 두 가지를 모두 말하는 것을 보았습니다. 텍스트를 증거로 인용 하십시오 . 다른 경우도 추가하십시오.
C ++ 17 업데이트
C ++ 17에서는 A_factory_func()
임시 객체 생성 (C ++ <= 14)에서 C ++ 17에서이 표현식이 초기화 된 (느슨하게 말하면) 객체의 초기화를 지정하는 것으로 변경되었습니다. 이러한 객체 ( "결과 객체"라고 함)는 선언 (예 a1
:), 초기화가 종료 될 때 생성 된 인공 객체 또는 참조 바인딩에 객체가 필요한 A_factory_func();
경우 (예 : 개체가 A_factory_func()
존재해야하는 변수 나 참조가 없기 때문에 "임시 구체화"라는 개체가 인위적으로 생성 됩니다.
우리의 경우 예로서,의 경우 a1
와 a2
특별한 규칙 같은 선언에서, 같은 유형의 초기화 prvalue의 결과 개체 말할 a1
변수 a1
때문에, 그리고 A_factory_func()
직접적으로 개체를 초기화합니다 a1
. A_factory_func(another-prvalue)
외부 prvalue의 결과 객체를 내부 prvalue의 결과 객체로 "통과" 하기 만하면 중개 기능 스타일 캐스트는 아무런 영향을 미치지 않습니다 .
A a1 = A_factory_func();
A a2(A_factory_func());
A_factory_func()
반환 되는 유형에 따라 다릅니다. A
복사 생성자가 명시 적 인 경우 첫 번째 생성자가 실패한다는 것을 제외하고 는 -를 반환한다고 가정합니다 . 8.6 / 14 읽기
double b1 = 0.5;
double b2(0.5);
이것은 내장 타입이기 때문에 똑같이하고 있습니다 (여기서는 클래스 유형이 아닙니다). 8.6 / 14를 읽으십시오 .
A c1;
A c2 = A();
A c3(A());
이것은 똑같이하지 않습니다. 첫 번째 A
는 비 POD 인 경우 기본적으로 초기화되며 POD에 대한 초기화를 수행하지 않습니다 ( 8.6 / 9 읽기 ). 두 번째 사본은 다음을 초기화합니다. 값을 임시로 초기화 한 다음 해당 값을 c2
( 5.2.3 / 2 및 8.6 / 14 읽기)에 복사합니다 . 물론 명시 적이 아닌 복사 생성자가 필요합니다 ( 8.6 / 14 및 12.3.1 / 3 및 13.3.1.3/1 읽기 ). 세 번째는 a c3
를 반환하고 a 를 반환하는 A
함수에 대한 함수 포인터를받는 함수에 대한 함수 선언을 만듭니다 A
( 8.2 참조 ).
초기화 직접 및 복사 초기화
그것들은 동일하게 보이고 동일하게 수행되어야하지만,이 두 형태는 경우에 따라 현저히 다릅니다. 초기화의 두 가지 형태는 직접 및 복사 초기화입니다.
T t(x);
T t = x;
우리는 그들 각각에 귀속 될 수있는 행동이 있습니다 :
- 직접 초기화는 오버로드 된 함수에 대한 함수 호출처럼 작동합니다.이 경우 함수는 (함수
T
포함explicit
) 의 생성자이며 인수는x
입니다. 과부하 해결은 가장 일치하는 생성자를 찾고 필요할 때 암시 적 변환이 필요합니다. - 복사 초기화는 암시 적 변환 시퀀스를 구성합니다 .
x
유형의 객체 로 변환하려고 합니다T
. 그런 다음 해당 객체를 초기화 된 객체로 복사 할 수 있으므로 복사 생성자도 필요하지만 아래에서는 중요하지 않습니다.
보시다시피, 복사 초기화는 어떤 방법으로 가능 암시 적 변환과 관련하여 직접 초기화의 일부입니다 : 직접 초기화를 호출 할 수있는 모든 생성자를 가지고 있으며, 반면 추가 가 인수 형식을 일치 할 필요가있는 암시 적 변환을 수행 할 수 있습니다, 초기화를 복사 하나의 암시 적 변환 시퀀스를 설정할 수 있습니다.
나는 열심히 노력 하여 생성자를 통해 "명백한"을 사용하지 않고 각 양식에 대해 다른 텍스트를 출력하는 다음 코드를 얻었습니다explicit
.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
어떻게 작동하고 왜 결과를 출력합니까?
직접 초기화
먼저 전환에 대해 아무것도 모릅니다. 생성자를 호출하려고 시도합니다. 이 경우 다음 생성자를 사용할 수 있으며 정확히 일치합니다 .
B(A const&)
해당 생성자를 호출하는 데 필요한 변환은 사용자 정의 변환보다 훨씬 적습니다 (여기서 const 한정 변환도 수행되지 않음). 따라서 직접 초기화가 호출됩니다.
복사 초기화
위에서 언급했듯이, 복사 초기화는
a
유형이B
없거나 파생 되지 않은 경우 변환 시퀀스를 구성 합니다 (여기서는 분명히 그렇습니다). 변환을 수행하는 방법을 찾고 다음 후보를 찾습니다.B(A const&) operator B(A&);
변환 함수를 어떻게 다시 작성했는지 주목하십시오. 매개 변수 유형
this
은 비 const 멤버 함수에서 비 const 에 대한 포인터 의 유형을 반영합니다 . 이제 우리는이 후보들을x
논쟁으로 부릅니다 . 승자가 변환 함수입니다. 두 개의 후보 함수가 모두 동일한 유형에 대한 참조를 허용하면 const 버전 이 적습니다 (이것은 비 const 멤버 함수를 선호하는 메커니즘입니다. -const 객체).변환 함수를 const 멤버 함수로 변경하면 변환이 모호합니다 (모두 유형의 매개 변수 유형이 있기 때문에
A const&
). Comeau 컴파일러는이를 거부하지만 GCC는이를 비-페 덴식 모드에서 받아들입니다.-pedantic
그래도 전환 하면 적절한 모호성 경고가 출력됩니다.
이 두 가지 형식이 어떻게 다른지 더 명확하게 만드는 데 도움이되기를 바랍니다.
할당 은 초기화 와 다릅니다 .
다음 두 줄 모두 초기화를 수행 합니다. 단일 생성자 호출이 수행됩니다.
A a1 = A_factory_func(); // calls copy constructor
A a1(A_factory_func()); // calls copy constructor
그러나 다음과 같습니다.
A a1; // calls default constructor
a1 = A_factory_func(); // (assignment) calls operator =
현재 이것을 증명할 텍스트가 없지만 실험하기는 매우 쉽습니다.
#include <iostream>
using namespace std;
class A {
public:
A() {
cout << "default constructor" << endl;
}
A(const A& x) {
cout << "copy constructor" << endl;
}
const A& operator = (const A& x) {
cout << "operator =" << endl;
return *this;
}
};
int main() {
A a; // default constructor
A b(a); // copy constructor
A c = a; // copy constructor
c = b; // operator =
return 0;
}
double b1 = 0.5;
암시 적 생성자의 호출입니다.
double b2(0.5);
명시 적 호출입니다.
차이점을 보려면 다음 코드를보십시오.
#include <iostream>
class sss {
public:
explicit sss( int )
{
std::cout << "int" << std::endl;
};
sss( double )
{
std::cout << "double" << std::endl;
};
};
int main()
{
sss ddd( 7 ); // calls int constructor
sss xxx = 7; // calls double constructor
return 0;
}
클래스에 명시 적 구성자가없는 경우 명시 적 호출과 내재적 호출이 동일합니다.
참고 사항 :
[12.2 / 1] Temporaries of class type are created in various contexts: ... and in some initializations (8.5).
즉, 복사 초기화에 사용됩니다.
[12.8 / 15] When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...
다시 말해서, 좋은 컴파일러는 피할 수있을 때 복사 초기화를위한 사본을 작성 하지 않습니다 . 대신 직접 초기화와 마찬가지로 생성자를 직접 호출합니다.
다시 말해, 복사 초기화는 이해할 수있는 코드가 작성된 대부분의 경우 <opinion>의 직접 초기화와 같습니다. 직접 초기화는 잠재적으로 임의 (따라서 알려지지 않은) 변환을 유발할 수 있으므로 가능하면 항상 복사 초기화를 사용하는 것을 선호합니다. (실제로 초기화하는 것처럼 보이는 보너스로) </ opinion>
기술적 인 고요함 : [12.2 / 1 위에서 계속] Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.
C ++ 컴파일러를 작성하지 않고 다행입니다.
첫 번째 그룹화 : A_factory_func
반환되는 항목에 따라 다릅니다 . 첫 번째 줄은 복사 초기화 의 예이고 두 번째 줄은 직접 초기화 입니다. 경우 A_factory_func
다시 표시 A
한 후 그들이 동일 객체는 둘 다 전화의 복사 생성자 A
, 그렇지 않으면 첫 번째 버전은 유형의를 rvalue 만들어 A
반환의 유형에 대해 사용 가능한 변환 사업자 A_factory_func
또는 적절한 A
생성자를 한 다음 구조에 복사 생성자를 호출 a1
이에서 일시적인. 두 번째 버전 A_factory_func
은 리턴 값을 취하거나 리턴 값을 내재적으로 변환 할 수있는 무언가를 취하는 적합한 생성자를 찾으려고합니다 .
두 번째 그룹화 : 내장 유형에는 이국적인 생성자가 없으므로 실제로 동일하다는 것을 제외하고는 정확히 동일한 논리가 유지됩니다.
세 번째 그룹화 : c1
기본적으로 초기화되며 c2
임시로 초기화 된 값에서 복사 초기화됩니다. 의 모든 구성원 c1
사용자 공급 기본 생성자 (있는 경우)를 명시 적으로 초기화하지 않는 경우 초기화되지 않을 수 있습니다 (등 등 또는 회원의 회원,) 포드 형이있다. 의 경우 c2
사용자 제공 복사 생성자가 있는지 여부와 해당 멤버를 적절히 초기화하는지 여부에 따라 달라 지지만 임시 멤버는 모두 초기화됩니다 (명시 적으로 초기화되지 않은 경우 0으로 초기화 됨). litb가 발견 한대로 c3
함정입니다. 실제로 함수 선언입니다.
이 부분에 대한 답변 :
A c2 = A (); c3 (A ());
대부분의 답변은 c-++ 11 이전이므로 c ++ 11이 이것에 대해 말한 것을 추가하고 있습니다.
단순 유형 지정자 (7.1.6.2) 또는 유형 이름 지정자 (14.6) 다음에 괄호로 묶인 expression-list는 표현식 목록에 지정된 유형의 값을 구성합니다. 표현식 목록이 단일 표현식 인 경우 유형 변환 표현식은 해당 캐스트 표현식 (5.4)과 동일합니다 (정의 된 의미로 정의 된 경우). 지정된 유형이 클래스 유형 인 경우 클래스 유형이 완료됩니다. 만약 표현리스트가 하나 이상의 값을 지정한다면, 그 유형은 적절하게 선언 된 생성자 (8.5, 12.1)를 가진 클래스가되어야하고 표현식 T (x1, x2, ...)는 선언 T t와 사실상 동일하다 (x1, x2, ...); 일부는 임시 변수 t를 발명했으며 결과는 t의 값을 prvalue로 사용합니다.
따라서 최적화 여부는 표준에 따라 동일합니다. 이것은 다른 답변에서 언급 한 내용과 일치합니다. 정확성을 위해 표준이 말한 것을 인용하십시오.
객체를 초기화 할 때 생성자 explicit
와 implicit
생성자 유형 의 차이를 볼 수 있습니다 .
클래스 :
class A
{
A(int) { } // converting constructor
A(int, int) { } // converting constructor (C++11)
};
class B
{
explicit B(int) { }
explicit B(int, int) { }
};
그리고 main
기능에서 :
int main()
{
A a1 = 1; // OK: copy-initialization selects A::A(int)
A a2(2); // OK: direct-initialization selects A::A(int)
A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int)
A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
A a5 = (A)1; // OK: explicit cast performs static_cast
// B b1 = 1; // error: copy-initialization does not consider B::B(int)
B b2(2); // OK: direct-initialization selects B::B(int)
B b3 {4, 5}; // OK: direct-list-initialization selects B::B(int, int)
// B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
B b5 = (B)1; // OK: explicit cast performs static_cast
}
기본적으로 생성자는 implicit
초기화하는 두 가지 방법이 있습니다.
A a1 = 1; // this is copy initialization
A a2(2); // this is direct initialization
그리고 explicit
하나의 방법으로 직접 구조를 정의함으로써 :
B b2(2); // this is direct initialization
B b5 = (B)1; // not problem if you either use of assign to initialize and cast it as static_cast
이러한 경우는 대부분 개체의 구현에 따라 달라 지므로 구체적인 대답을하기가 어렵습니다.
사건을 고려
A a = 5;
A a(5);
이 경우 단일 정수 인수를 허용하는 적절한 할당 연산자 및 초기화 생성자를 가정하면, 상기 메소드를 구현하는 방법은 각 행의 동작에 영향을 미칩니다. 그러나 중복 코드를 제거하기 위해 구현에서 다른 하나를 호출하는 것이 일반적입니다 (단순한 경우 실제 목적은 없습니다).
편집 : 다른 응답에서 언급했듯이 첫 번째 줄은 실제로 복사 생성자를 호출합니다. 할당 연산자와 관련된 주석은 독립형 할당과 관련된 동작으로 간주하십시오.
즉, 컴파일러가 코드를 최적화하는 방법은 자체 영향을 미칩니다. "="연산자를 호출하는 초기화 생성자가있는 경우-컴파일러에서 최적화를 수행하지 않으면 맨 위 줄이 맨 아래 줄과 달리 두 번의 점프를 수행합니다.
이제 가장 일반적인 상황에서 컴파일러는 이러한 경우를 최적화하여 이러한 유형의 비 효율성을 제거합니다. 따라서 당신이 묘사하는 다른 모든 상황은 사실상 똑같을 것입니다. 정확히 무슨 일이 일어나고 있는지 보려면 컴파일러의 객체 코드 또는 어셈블리 출력을 볼 수 있습니다.
'Programing' 카테고리의 다른 글
git difftool, 직렬이 아닌 모든 diff 파일을 즉시여십시오. (0) | 2020.04.17 |
---|---|
Visual Studio 2012 웹 게시에서 파일을 복사하지 않습니다 (0) | 2020.04.17 |
System.currentTimeMillis () vs. 새로운 Date () vs. Calendar.getInstance (). getTime () (0) | 2020.04.17 |
실행 스크립트의 경로 결정 (0) | 2020.04.17 |
Java, "for each"루프에서 현재 색인 / 키를 얻는 방법 (0) | 2020.04.17 |