하나의 함수 호출 C ++를 사용하여 여러 상수 클래스 멤버 초기화


50

동일한 함수 호출을 기반으로 초기화 해야하는 두 개의 다른 상수 멤버 변수가있는 경우 함수를 두 번 호출하지 않고이를 수행하는 방법이 있습니까?

예를 들어 분자와 분모가 일정한 분수 클래스입니다.

int gcd(int a, int b); // Greatest Common Divisor
class Fraction {
public:
    // Lets say we want to initialize to a reduced fraction
    Fraction(int a, int b) : numerator(a/gcd(a,b)), denominator(b/gcd(a,b))
    {

    }
private:
    const int numerator, denominator;
};

GCD 함수가 두 번 호출되므로 시간이 낭비됩니다. 새 반원을 정의 할 수도 있습니다.gcd_a_b 하고 먼저 초기화 목록의 gcd 출력을 할당하면 메모리 낭비가 발생합니다.

일반적으로 함수 호출이나 메모리 낭비 없이이 작업을 수행하는 방법이 있습니까? 초기화 목록에 임시 변수를 만들 수 있습니까? 감사합니다.


5
"GCD 기능이 두 번 호출된다"는 증거가 있습니까? 두 번 언급되었지만, 두 번 호출하는 코드를 생성하는 컴파일러와는 다릅니다. 컴파일러는 순수한 함수라고 추론하고 두 번째 언급에서 해당 값을 재사용 할 수 있습니다.
Eric Towers

6
@EricTowers : 그렇습니다. 컴파일러는 경우에 따라 실제로 문제를 해결할 수도 있습니다. 그러나 그들이 정의 (또는 객체의 주석)를 볼 수있는 경우에만 그렇지 않으면 순수한 것을 증명할 방법이 없습니다. 당신은 해야 링크 - 타임 최적화가 활성화 컴파일하지만, 모두가 않습니다. 그리고 함수는 라이브러리에있을 수 있습니다. 또는 함수의 경우는 고려 하지 부작용을 가지고, 그리고 정확히 한 번 호출하면 정확성의 문제인가?
Peter Cordes

@EricTowers 재미있는 포인트. 실제로 GCD 함수에 print 문을 넣어서 확인하려고 시도했지만 이제는 순수한 함수가되는 것을 막을 수 있음을 알고 있습니다.
Qq0

@ Qq0 : gcc 또는 clang과 함께 Godbolt 컴파일러 탐색기 를 사용하여 컴파일러에서 생성 된 asm을 확인하여 확인할 수 있습니다 -O3. 그러나 아마도 간단한 테스트 구현의 경우 실제로 함수 호출을 인라인 할 것입니다. __attribute__((const))가시적 인 정의를 제공하지 않고 프로토 타입 을 사용 하거나 순수하게 사용하는 경우 GCC 또는 clang이 동일한 인수를 사용하여 두 호출간에 공통 하위 식 제거 (CSE)를 수행하도록해야합니다. Drew의 대답은 순수하지 않은 함수에서도 작동하므로 훨씬 좋으며 func가 인라인되지 않을 때마다 사용해야합니다.
Peter Cordes

일반적으로 비 정적 const 멤버 변수는 피하는 것이 가장 좋습니다. const 모든 것이 자주 적용되지 않는 몇 가지 영역 중 하나입니다. 예를 들어 클래스 객체를 할당 할 수 없습니다. 용량 제한이 크기 조정에 영향을 미치지 않는 한 벡터로 emplace_back을 벡터로 넣을 수 있습니다.
더그

답변:


67

일반적으로 함수 호출이나 메모리 낭비 없이이 작업을 수행하는 방법이 있습니까?

예. 이것은 C ++ 11에 도입 된 위임 생성자 로 수행 할 수 있습니다 .

위임하는 생성자는 전에 건설에 필요한 임시 값을 취득 할 수있는 매우 효율적인 방법입니다 어떤 멤버 변수가 초기화됩니다.

int gcd(int a, int b); // Greatest Common Divisor
class Fraction {
public:
    // Call gcd ONCE, and forward the result to another constructor.
    Fraction(int a, int b) : Fraction(a,b,gcd(a,b))
    {
    }
private:
    // This constructor is private, as it is an
    // implementation detail and not part of the public interface.
    Fraction(int a, int b, int g_c_d) : numerator(a/g_c_d), denominator(b/g_c_d)
    {
    }
    const int numerator, denominator;
};

다른 생성자 호출로 인한 오버 헤드가 중요합니까?
Qq0

1
@ Qq0 보통 최적화가 활성화 된 경우 오버 헤드가 없음을 알 수 있습니다 .
Drew Dormann

2
@ Qq0 : C ++는 최신 최적화 컴파일러를 중심으로 설계되었습니다. .h실제 생성자 정의가 인라인에 표시되지 않더라도 클래스 위임에서 클래스를 볼 수있게하는 경우이 위임을 쉽게 인라인 할 수 있습니다 . 즉, gcd()호출은 각 생성자 호출 사이트에 인라인되고 call3- 오퍼랜드 개인 생성자에는 a 만 남습니다 .
Peter Cordes

10

멤버 변수는 클래스 선언에서 선언 된 순서대로 초기화되므로 다음을 수행 할 수 있습니다 (수학적)

#include <iostream>
int gcd(int a, int b){return 2;}; // Greatest Common Divisor of (4, 6) just to test
class Fraction {
public:
    // Lets say we want to initialize to a reduced fraction
    Fraction(int a, int b) : numerator{a/gcd(a,b)}, denominator(b/(a/numerator))
    {

    }
//private:
    const int numerator, denominator;//make sure that they are in this order
};
//Test
int main(){
    Fraction f{4,6};
    std::cout << f.numerator << " / " << f.denominator;
}

다른 생성자를 호출하거나 만들 필요가 없습니다.


6
좋아, 그것은 GCD에 특별히 작동하지만 다른 많은 유스 케이스는 아마도 args와 첫 번째에서 두 번째 const를 파생시킬 수는 없습니다. 그리고 작성된 것처럼 이것은 컴파일러가 최적화하지 못할 수도있는 또 다른 단점 인 이상적인 하나의 추가 부문을 가지고 있습니다. GCD는 하나의 디비전 만 필요하므로 GCD를 두 번 호출하는 것만 큼 나쁠 수 있습니다. (그 부서가 현대 CPU에서 자주하는 것처럼 다른 운영 비용을 지배한다고 가정합니다.)
Peter Cordes

@PeterCordes이지만 다른 솔루션에는 추가 함수 호출이 있으며 더 많은 명령 메모리를 할당합니다.
asmmo

1
Drew의 위임 생성자에 대해 이야기하고 있습니까? 그렇게하면 Fraction(a,b,gcd(a,b))위임을 발신자 에게 인라인 할 수 있어 총 비용이 줄어 듭니다. 이 인라이닝은 컴파일러에서 여분의 나누기를 취소하는 것보다 쉽습니다. godbolt.org에서 시도하지는 않았지만 궁금한 점이 있다면 가능합니다. -O3일반 빌드처럼 gcc 또는 clang 을 사용하십시오. (C ++는 최신 최적화 컴파일러를 가정하여 설계되었으므로 기능은 다음과 같습니다. constexpr)
Peter Cordes

-3

@Drew Dormann은 내가 생각했던 것과 비슷한 솔루션을 제공했습니다. OP는 ctor를 수정할 수 없다고 언급하지 않았으므로 Fraction f {a, b, gcd(a, b)}다음 과 같이 호출 할 수 있습니다 .

Fraction(int a, int b, int tmp): numerator {a/tmp}, denominator {b/tmp}
{
}

이 방법으로 만 함수, 생성자 또는 기타에 대한 두 번째 호출이 없으므로 시간 낭비가 없습니다. 어쨌든 임시 메모리를 만들어야하므로 메모리를 낭비하지 않으므로 메모리를 잘 활용할 수 있습니다. 또한 여분의 분할을 피합니다.


3
편집하면 질문에 대답하지 않습니다. 이제 발신자가 3 번째 인수를 전달해야합니까? 생성자 본문 내에서 할당을 사용하는 원래 버전 const은에서 작동하지 않지만 적어도 다른 유형에서는 작동합니다. 그리고 당신은 또한 "추가"를 피하고 있습니까? asmmo의 답변을 의미합니까?
Peter Cordes

1
자, 요점을 설명 했으므로 이제 downvote를 제거했습니다. 그러나 이것은 꽤 끔찍한 것처럼 보이며 모든 호출자에게 생성자 작업 중 일부를 수동으로 인라인해야합니다. 이것은 DRY (반복하지 말 것)의 반대이며 클래스의 책임 / 내부를 캡슐화합니다. 대부분의 사람들은 이것을 수용 가능한 해결책으로 생각하지 않을 것입니다. 이것을 깨끗하게 수행하는 C ++ 11 방법이 있다는 것을 감안할 때 이전 C ++ 버전에 갇혀 있거나 클래스 에이 생성자에 대한 호출이 거의 없다면 아무도이 작업을 수행해서는 안됩니다.
Peter Cordes

2
@aconcernedcitizen : 나는 성능상의 이유가 아니라 코드 품질의 이유를 의미한다. 당신의 방법으로,이 클래스의 내부 작동 방식을 변경 한 경우 생성자에 대한 모든 호출을 찾아서 세 번째 인수를 변경해야합니다. 그 여분은 ,gcd(foo, bar)따라서 모든 callsite 밖으로 고려되어야한다 수있는 여분의 코드 소스의 . 이는 성능이 아니라 유지 관리 / 가독성 문제입니다. 컴파일러는 컴파일 타임에 성능을 위해 인라인 할 가능성이 높습니다.
Peter Cordes

1
@PeterCordes 당신이 옳습니다. 이제 저는 제 생각이 해결책에 고정되어 있고 다른 모든 것을 무시했습니다. 어느 쪽이든, 부끄러움을 위해서만 대답이 유지됩니다. 그것에 대해 의문이 생길 때마다 어디를 찾아야하는지 알게 될 것입니다.
관심있는 시민

1
또한의 경우 고려해야 Fraction f( x+y, a+b ); 기록 할의 그것을 쓰기 방법을, 당신은 것 BadFraction f( x+y, a+b, gcd(x+y, a+b) );또는 사용 tmp에 바르. 또는 더 나쁜 것은, 작성하려는 경우 Fraction f( foo(x), bar(y) );-반환 값을 보유하기 위해 일부 tmp vars를 선언하거나 해당 함수를 다시 호출하고 컴파일러가 CSE를 피하기를 희망하는 콜 사이트가 필요하다는 것입니다. 한 호출자가 args를 믹싱하는 경우를 디버그하고 싶 gcd습니까? 실제로 생성자에 전달 된 첫 번째 2 args의 GCD가 아닙니다. 아니? 그런 다음 그 버그를 가능하게하지 마십시오.
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.