design-patterns 팩토리 - C++에서 factory 메소드 패턴을 올바르게 구현하는 방법




피자 메서드 (9)

C ++에는 꽤 오랜 시간 동안 불편 함을 느끼게합니다. 왜냐하면 솔직히 말해서 나는 그것을 어떻게 할 지 모르기 때문입니다.

C ++에서 Factory Method를 올바르게 구현하려면 어떻게해야합니까?

목표 : 클라이언트가 허용되지 않는 결과와 성능 저하없이 객체의 생성자 대신에 팩토리 메소드를 사용하여 일부 객체를 인스턴스화 할 수 있도록 허용합니다.

"팩토리 메서드 패턴"이란, 개체 내부의 정적 팩터 리 메서드 또는 다른 클래스에 정의 된 메서드 또는 전역 함수를 의미합니다. 일반적으로 "클래스 X 인스턴스화의 일반적인 방법을 생성자가 아닌 다른 곳으로 리디렉션하는 개념"입니다.

내가 생각한 몇 가지 가능한 답을 훑어 보자.

0) 공장을 만들지 말고, 생성자를 만드십시오.

좋은 (실제로는 가장 좋은 해결책) 소리가 들리지만 일반적인 치료법은 아닙니다. 우선, 객체 생성이 다른 클래스로의 추출을 정당화하기에 충분히 복잡한 작업 인 경우가 있습니다. 그러나 심지어는 사실을 제쳐 놓고, 단지 생성자를 사용하는 단순한 객체의 경우조차도 그렇게하지 않을 수 있습니다.

제가 아는 가장 간단한 예제는 2-D Vector 클래스입니다. 그렇게 간단하면서도 까다 롭습니다. 나는 직교 좌표와 극좌표 모두에서 그것을 만들 수 있기를 원한다. 분명히, 나는 할 수 없다 :

struct Vec2 {
    Vec2(float x, float y);
    Vec2(float angle, float magnitude); // not a valid overload!
    // ...
};

내 사고 방식은 다음과 같습니다.

struct Vec2 {
    static Vec2 fromLinear(float x, float y);
    static Vec2 fromPolar(float angle, float magnitude);
    // ...
};

생성자 대신 정적 팩토리 메서드를 사용하게되는데, 필자는 기본적으로 어떤 식 으로든 (즉 클래스가 자체 공장이되는) 팩터 리 패턴을 구현한다는 것을 의미합니다. 이것은 멋지게 보이지만 (이 특별한 경우에 맞을 것입니다.), 어떤 경우에는 실패합니다. 2 번에서 설명 할 것입니다. 계속 읽어보십시오.

또 다른 경우 : 일부 API의 두 가지 불투명 한 typedef (관련이없는 도메인의 GUID 또는 GUID와 비트 필드와 같은)로 오버로드하려고 시도하고, 의미 상으로 완전히 다른 유형 (이론적으로 - 유효한 오버로드)을 입력하지만 실제적으로 같은 것 - 부호없는 int 나 void 포인터처럼.

1) 자바 방식

Java는 동적 할당 객체 만 가지고 있기 때문에 간단합니다. 공장 만들기는 다음과 같이 사소한 것입니다.

class FooFactory {
    public Foo createFooInSomeWay() {
        // can be a static method as well,
        //  if we don't need the factory to provide its own object semantics
        //  and just serve as a group of methods
        return new Foo(some, args);
    }
}

C ++에서 이것은 다음과 같이 번역됩니다.

class FooFactory {
public:
    Foo* createFooInSomeWay() {
        return new Foo(some, args);
    }
};

시원한? 실제로, 종종. 그러나 그런 다음에는 사용자가 동적 할당 만 사용해야합니다. 정적 할당은 C ++을 복잡하게 만드는 요인이지만, 종종 강력하게 만듭니다. 또한 동적 할당을 허용하지 않는 일부 대상 (키워드 : 포함)이 있다고 생각합니다. 그리고 이것은 그러한 플랫폼의 사용자가 깨끗한 OOP를 작성하는 것을 의미하지는 않습니다.

어쨌든, 철학은 제쳐두고 : 일반적인 경우, 나는 공장의 사용자들이 동적 인 할당에 묶이지 않도록하고 싶지 않다.

2) 가치에 의한 반환

좋습니다, 그래서 우리는 동적 할당을 원할 때 1) 멋지다는 것을 압니다. 정적 할당을 추가하지 않는 이유는 무엇입니까?

class FooFactory {
public:
    Foo* createFooInSomeWay() {
        return new Foo(some, args);
    }
    Foo createFooInSomeWay() {
        return Foo(some, args);
    }
};

뭐? 우리는 리턴 타입으로 오버로드 할 수 없습니까? 오 물론 우리는 할 수 없습니다. 메소드 이름을 변경하여이를 반영하도록합시다. 그리고 네, 메소드 이름을 바꿀 필요가 없다는 것을 얼마나 강조했는지를 강조하기 위해 위의 잘못된 코드 예제를 작성했습니다. 예를 들어 이름을 변경해야하므로 언어에 구애받지 않는 팩토리 디자인을 제대로 구현할 수 없기 때문입니다. 이 코드의 모든 사용자는 사양과 구현의 차이점을 기억해야합니다.

class FooFactory {
public:
    Foo* createDynamicFooInSomeWay() {
        return new Foo(some, args);
    }
    Foo createFooObjectInSomeWay() {
        return Foo(some, args);
    }
};

OK ... 우리는 그것을 가지고있다. 메서드 이름을 변경해야하기 때문에 추한 것입니다. 동일한 코드를 두 번 작성해야하기 때문에 불완전합니다. 그러나 일단 끝나면 작동합니다. 권리?

글쎄, 보통. 그러나 때때로 그렇지 않습니다. Foo를 만들 때, 우리는 실제로 컴파일러에 의존하여 반환 값 최적화를 수행합니다. 왜냐하면 C ++ 표준은 컴파일러 공급 업체가 언제 어디에서 객체를 만들지를 지정하지 않아도되고 객체를 반환 할 때 언제 복사 될지 지정하지 않기 때문에 C ++에서 값에 의한 임시 객체. 따라서 Foo가 복사하는 데 비용이 많이 든다면이 방법은 위험합니다.

Foo가 전혀 복사 할 수 없다면 어떨까요? 글쎄. ( copy elision이 보장 된 C ++ 17에서는 위의 코드에 대해 더 이상 복사 할 수 없다는 점에 유의하십시오 )

결론 : 객체를 반환하여 팩토리를 만드는 것은 실제로 몇몇 경우 (예 : 앞에서 언급 한 2-D 벡터)의 솔루션이지만, 여전히 생성자를 대체하는 것은 아닙니다.

3) 2 상 공사

다른 사람이 생각해내는 또 다른 사항은 객체 할당과 초기화의 문제를 분리하는 것입니다. 일반적으로 다음과 같은 코드가 생성됩니다.

class Foo {
public:
    Foo() {
        // empty or almost empty
    }
    // ...
};

class FooFactory {
public:
    void createFooInSomeWay(Foo& foo, some, args);
};

void clientCode() {
    Foo staticFoo;
    auto_ptr<Foo> dynamicFoo = new Foo();
    FooFactory factory;
    factory.createFooInSomeWay(&staticFoo);
    factory.createFooInSomeWay(&dynamicFoo.get());
    // ...
}

사람은 그것이 매력처럼 작동한다고 생각할 수도 있습니다. 코드에서 우리가 지불해야하는 유일한 가격은 ...

나는이 모든 것을 쓰고 이것을 마지막으로 남겨 두었으므로 나는 그것을 싫어해야합니다. :) 왜?

무엇보다도 ... 나는 진실로 2 단계 건설의 개념을 싫어하며, 내가 그것을 사용할 때 죄책감을 느낀다. "존재한다면 그것이 유효한 상태"라는 주장을 가지고 객체를 디자인하면 내 코드가 안전하고 오류가 발생하기 쉽지 않습니다. 나는 그 방법을 좋아한다.

그 컨벤션을 버리고 공장을 만들기위한 목적으로 내 물건의 디자인을 변경하는 것은 .. 잘, 다루기 힘든 일입니다.

위의 내용이 많은 사람들을 납득시키지 않을 것이라는 것을 알고 있으므로 좀 더 확실한 주장을 해 둡니다. 2 단계 구성을 사용하면 다음 작업을 수행 할 수 없습니다.

  • const 또는 참조 멤버 변수 초기화,
  • 인수를 기본 클래스 생성자 및 멤버 객체 생성자에 전달합니다.

아마도 지금 당장 생각할 수없는 몇 가지 단점이있을 수 있습니다. 그리고 위의 글 머리 기호가 나에게 이미 설득력을 발휘 했으므로 특별히 고민하지 않아도됩니다.

그래서 : 공장을 구현하기위한 좋은 일반적인 해결책에 근접하지 않습니다.

결론 :

우리는 다음과 같은 객체 인스턴스화 방법을 원합니다.

  • 할당에 관계없이 균일 한 인스턴스 생성을 허용하고,
  • 건설 방법에 다른 의미있는 이름을 붙이십시오 (따라서 인수에 의한 과부하에 의존하지 마십시오).
  • 특히 클라이언트 측에서는 중요한 성능 히트를 유발하지 않으며, 중요한 코드가 부풀어 오르는 것이 바람직합니다.
  • 다음과 같이 일반적인 것 : 어떤 수업에도 소개 될 수 있습니다.

내가 언급 한 방법이 그 요구 사항을 충족시키지 못한다는 것을 증명했다고 나는 믿는다.

어떤 힌트? 제게 해결책을 제공해주세요. 저는이 언어가 제게 사소한 개념을 제대로 구현하지 못하리라고 생각하고 싶지 않습니다.


Answers

나는 대부분 받아 들인 대답에 동의하지만, 기존 답변에서 다루지 않은 C ++ 11 옵션이 있습니다 :

  • 값으로 팩터 리 메소드 결과 반환합니다.
  • 값싼 이동 생성자를 제공하십시오.

예:

struct sandwich {
  // Factory methods.
  static sandwich ham();
  static sandwich spam();
  // Move constructor.
  sandwich(sandwich &&);
  // etc.
};

그런 다음 스택에 객체를 만들 수 있습니다.

sandwich mine{sandwich::ham()};

다른 것들의 하위 객체로서 :

auto lunch = std::make_pair(sandwich::spam(), apple{});

또는 동적 할당 :

auto ptr = std::make_shared<sandwich>(sandwich::ham());

언제 이것을 사용할 수 있습니까?

public 생성자에서 예비 계산없이 모든 클래스 멤버에게 의미있는 초기화 프로그램을 제공 할 수없는 경우 해당 생성자를 정적 메서드로 변환 할 수 있습니다. 정적 메서드는 예비 계산을 수행 한 다음 멤버 별 초기화 만 수행하는 개인 생성자를 통해 값 결과를 반환합니다.

어떤 접근 방식이 불필요하게 비효율적이지 않으면 서 가장 명확한 코드를 제공하는지에 따라 달라질 수 있으므로 ' '이라고 말합니다.


당신은 전혀 공장을 사용하지 않는 것에 대해 생각해 보았고 대신 타입 시스템을 잘 사용 했습니까? 이런 종류의 일을하는 두 가지 접근 방식을 생각해 볼 수 있습니다.

옵션 1:

struct linear {
    linear(float x, float y) : x_(x), y_(y){}
    float x_;
    float y_;
};

struct polar {
    polar(float angle, float magnitude) : angle_(angle),  magnitude_(magnitude) {}
    float angle_;
    float magnitude_;
};


struct Vec2 {
    explicit Vec2(const linear &l) { /* ... */ }
    explicit Vec2(const polar &p) { /* ... */ }
};

다음과 같이 작성할 수 있습니다.

Vec2 v(linear(1.0, 2.0));

옵션 2 :

iterators 등에서 STL처럼 "태그"를 사용할 수 있습니다. 예 :

struct linear_coord_tag linear_coord {}; // declare type and a global
struct polar_coord_tag polar_coord {};

struct Vec2 {
    Vec2(float x, float y, const linear_coord_tag &) { /* ... */ }
    Vec2(float angle, float magnitude, const polar_coord_tag &) { /* ... */ }
};

이 두 번째 방법을 사용하면 다음과 같은 코드를 작성할 수 있습니다.

Vec2 v(1.0, 2.0, linear_coord);

또한 각 생성자마다 고유 한 프로토 타입을 가질 수있게 해주는 동시에 멋지고 표현력이 좋습니다.


Loki는 Factory MethodAbstract Factory를 모두 가지고 있습니다. 둘 다 Andei Alexandrescu의 Modern C ++ Design 에 광범위하게 문서화되어 있습니다. 팩토리 메서드는 아마도 여러분이 보였던 것과 더 가깝습니다.하지만 여전히 조금 다릅니다 (적어도 메모리가 제공되면 팩터 리가 해당 유형의 객체를 생성하기 전에 유형을 등록해야합니다).


공장 패턴

class Point
{
public:
  static Point Cartesian(double x, double y);
private:
};

그리고 만약 컴파일러가 리턴 값 최적화를 지원하지 않는다면, 아마 많은 최적화를 포함하지 않을 것입니다 ...


우선, 객체 생성이 다른 클래스로의 추출을 정당화하기에 충분히 복잡한 작업 인 경우가 있습니다.

나는이 점이 부정확하다고 생각한다. 복잡성은 별 문제가되지 않습니다. 관련성은 무엇입니까. 객체가 한 단계로 작성 될 수 있다면 (빌더 패턴과 유사하지 않음), 생성자는이를 수행하기에 적합한 장소입니다. 작업을 수행하는 데 정말로 다른 클래스가 필요한 경우에는 생성자에서 사용되는 도우미 클래스 여야합니다.

Vec2(float x, float y);
Vec2(float angle, float magnitude); // not a valid overload!

이에 대한 쉬운 해결 방법이 있습니다.

struct Cartesian {
  inline Cartesian(float x, float y): x(x), y(y) {}
  float x, y;
};
struct Polar {
  inline Polar(float angle, float magnitude): angle(angle), magnitude(magnitude) {}
  float angle, magnitude;
};
Vec2(const Cartesian &cartesian);
Vec2(const Polar &polar);

유일한 단점은 약간 장황 해 보입니다.

Vec2 v2(Vec2::Cartesian(3.0f, 4.0f));

그러나 좋은 점은 사용중인 좌표 유형을 즉시 볼 수 있으며 동시에 복사에 대해 걱정할 필요가 없다는 것입니다. 복사를 원한다면 비용이 많이 들고 (프로파일 링에 의해 입증 된 것처럼), 오버 헤드 복사를 피하기 위해 Qt의 공유 클래스 와 같은 것을 사용할 수도 있습니다.

할당 유형에서 팩토리 패턴을 사용하는 주요 이유는 대개 다형성입니다. 생성자는 가상 일 수 없으며 가능한 경우에도 의미가 없습니다. 정적 또는 스택 할당을 사용할 때 컴파일러가 정확한 크기를 알아야하기 때문에 다형성 방식으로 객체를 만들 수 없습니다. 그래서 포인터와 참조에서만 작동합니다. 그리고 객체를 기술적으로 참조로 삭제할 수는 있지만 혼란스럽고 버그가 발생하기 쉽기 때문에 공장에서 참조를 반환하는 것은 효과가 없습니다 . C ++ 참조 변수를 반환하는 연습은 악의적입니까? 예를 들면. 따라서 포인터 만 남았고 스마트 포인터도 포함됩니다. 즉, 동적 할당을 사용하면 팩토리가 가장 유용하므로 다음과 같은 작업을 수행 할 수 있습니다.

class Abstract {
  public:
    virtual void do() = 0;
};

class Factory {
  public:
    Abstract *create();
};

Factory f;
Abstract *a = f.create();
a->do();

다른 경우에는 공장에서 언급 한 과부하 문제와 같은 사소한 문제를 해결하는 데 도움이됩니다. 그것들을 통일 된 방식으로 사용하는 것이 가능하다면 좋겠지 만 그것은 아마도 불가능하다는 것을 많이 상하게하지는 않습니다.



나는 그것이 너무 광범위하다고 믿기 때문에 나의 모든 질문에 답하려고하지 않는다. 몇 가지 메모 만 있습니다.

객체 생성이 다른 클래스로의 추출을 정당화하기에 충분히 복잡한 작업 인 경우가 있습니다.

그 클래스는 사실 공장이 아니라 Builder 입니다.

일반적인 경우, 나는 공장의 사용자가 동적 인 할당에 묶이지 않도록하고 싶지 않다.

그런 다음 공장에서 스마트 포인터로 캡슐화 할 수 있습니다. 나는 네가 너의 케이크를 가지고 그것을 먹을 수있는 이런 식으로 믿는다.

이는 또한 가치에 의한 반환과 관련된 문제를 제거합니다.

결론 : 객체를 반환하여 팩토리를 만드는 것은 실제로 몇몇 경우 (예 : 앞에서 언급 한 2-D 벡터)의 솔루션이지만, 여전히 생성자를 대체하는 것은 아닙니다.

과연. 모든 디자인 패턴에는 (언어 별) 제약 사항과 단점이 있습니다. 문제를 해결하는 데 도움이되는 경우에만 사용하는 것이 좋습니다.

"완벽한"공장 구현이 끝나면, 행운을 빈다.


당신은 아주 좋은 해결책을 읽을 수 있습니다 : http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

가장 좋은 해결책은 "의견 및 토론"에 있습니다. "정적 만들기 방법 필요 없음"을 참조하십시오.

이 아이디어에서 저는 공장을했습니다. Qt를 사용하고 있지만 QMap과 QString을 std에 상응하는 것으로 변경할 수 있습니다.

#ifndef FACTORY_H
#define FACTORY_H

#include <QMap>
#include <QString>

template <typename T>
class Factory
{
public:
    template <typename TDerived>
    void registerType(QString name)
    {
        static_assert(std::is_base_of<T, TDerived>::value, "Factory::registerType doesn't accept this type because doesn't derive from base class");
        _createFuncs[name] = &createFunc<TDerived>;
    }

    T* create(QString name) {
        typename QMap<QString,PCreateFunc>::const_iterator it = _createFuncs.find(name);
        if (it != _createFuncs.end()) {
            return it.value()();
        }
        return nullptr;
    }

private:
    template <typename TDerived>
    static T* createFunc()
    {
        return new TDerived();
    }

    typedef T* (*PCreateFunc)();
    QMap<QString,PCreateFunc> _createFuncs;
};

#endif // FACTORY_H

샘플 사용법 :

Factory<BaseClass> f;
f.registerType<Descendant1>("Descendant1");
f.registerType<Descendant2>("Descendant2");
Descendant1* d1 = static_cast<Descendant1*>(f.create("Descendant1"));
Descendant2* d2 = static_cast<Descendant2*>(f.create("Descendant2"));
BaseClass *b1 = f.create("Descendant1");
BaseClass *b2 = f.create("Descendant2");

먼저, 언어 변호사처럼 생각하는 법을 배워야합니다.

C ++ 사양은 특정 컴파일러, 운영 체제 또는 CPU를 참조하지 않습니다. 실제 시스템의 일반화 된 추상 기계 를 참조합니다. 언어 변호사의 세계에서 프로그래머는 추상적 기계 용 코드를 작성해야합니다. 컴파일러의 역할은 콘크리트 시스템에서 그 코드를 실현하는 것입니다. 스펙을 엄격하게 코딩하면 현재 또는 향후 50 년 동안 호환되는 C ++ 컴파일러가있는 시스템에서 코드를 수정하지 않고 컴파일하고 실행할 수 있습니다.

C ++ 98 / C ++ 03 사양의 추상 기계는 근본적으로 단일 스레드입니다. 따라서 스펙과 관련하여 "완벽하게 이식 가능"한 멀티 쓰레드 C ++ 코드를 작성하는 것은 불가능합니다. 이 스펙은 메모리로드 및 스토어의 원 자성 또는로드 및 저장이 발생할 수있는 순서 에 대해 아무 것도 말하지 않으며 뮤텍스와 같은 것을 신경 쓰지 않습니다.

물론 pthread 나 Windows와 같은 특정 구체적인 시스템에 대해 실제로 멀티 스레드 코드를 작성할 수 있습니다. 그러나 C ++ 98 / C ++ 03에 대한 다중 스레드 코드를 작성하는 표준 방법은 없습니다.

C ++ 11의 추상 기계는 설계 상 멀티 스레드입니다. 또한 잘 정의 된 메모리 모델을 가지고 있습니다 . 즉, 컴파일러가 메모리에 액세스 할 때 수행 할 수도 있고 수행하지 않을 수도있는 작업을 말합니다.

두 개의 스레드에 의해 한 쌍의 전역 변수가 동시에 액세스되는 다음 예제를 고려하십시오.

           Global
           int x, y;

Thread 1            Thread 2
x = 17;             cout << y << " ";
y = 37;             cout << x << endl;

스레드 2 출력은 무엇입니까?

C ++ 98 / C ++ 03에서는 이것이 정의되지 않은 동작조차되지 않습니다. 표준은 "스레드"라고하는 것을 고려하지 않기 때문에 질문 자체는 의미 가 없습니다.

C ++ 11에서로드 및 저장은 일반적으로 원자적일 필요가 없으므로 결과는 정의되지 않은 동작입니다. 어느 정도 향상되지 않은 것처럼 보일 수도 있습니다 ... 그리고 그 자체로는 그렇지 않습니다.

하지만 C ++ 11에서는 다음과 같이 작성할 수 있습니다.

           Global
           atomic<int> x, y;

Thread 1                 Thread 2
x.store(17);             cout << y.load() << " ";
y.store(37);             cout << x.load() << endl;

이제 상황이 훨씬 더 재미 있습니다. 우선, 여기서의 행동이 정의됩니다 . 이제 스레드 2는 0 0 (스레드 1 이전에 실행되는 경우), 37 17 (스레드 1 이후에 실행되는 경우) 또는 0 17 (스레드 1이 x에 할당 된 후 y에 할당되기 전에 실행되는 경우)를 인쇄 할 수 있습니다.

C ++ 11의 원자 적로드 / 저장소에 대한 기본 모드는 순차적 일관성 을 적용하기 때문에 인쇄 할 수없는 것은 37 0 입니다. 이것은 단순히 모든로드 및 저장소가 각 스레드 내에서 작성한 순서대로 "있는 그대로"있어야한다는 것을 의미하지만 스레드간에 작업을 인터리브 할 수 있지만 시스템에서는 좋아합니다. 따라서 원자의 기본 동작은로드 및 저장에 원 자성순서 를 제공합니다.

이제 현대 CPU에서 순차적 일관성을 유지하는 데 많은 비용이 듭니다. 특히, 컴파일러는 모든 액세스간에 본격적인 메모리 장벽을 내고 있습니다. 그러나 알고리즘이 순서가 잘못된로드 및 저장을 허용 할 수 있다면 즉, 원 자성이 필요하지만 순서가 필요하지 않은 경우; 즉,이 프로그램의 출력으로 37 0 을 허용 할 수 있다면 다음과 같이 작성할 수 있습니다.

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

CPU가 현대적 일수록 이전 예제보다 더 빠를 가능성이 높습니다.

마지막으로, 특정로드 및 저장을 순서대로 유지해야하는 경우 다음을 작성할 수 있습니다.

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

이렇게하면 순서가 지정된로드 및 저장으로 되돌아갑니다. 따라서 37 0 은 더 이상 출력이 불가능하지만 최소한의 오버 헤드로 수행됩니다. (이 간단한 예제에서는 결과가 본격적인 일관성과 동일하지만 큰 프로그램에서는 그렇지 않습니다.)

물론보고 싶은 출력이 0 0 또는 37 17 이면 원래 코드 주위에 뮤텍스를 래핑 할 수 있습니다. 그러나 당신이 이것을 멀리 읽었다면, 당신은 이미 그것이 어떻게 작동하는지 알았을 것입니다. 그리고이 답변은 이미 의도 한 것보다 더 오래갑니다 :-).

그래서 결론. 뮤텍스는 훌륭하고 C ++ 11에서는이를 표준화합니다. 하지만 때로는 성능상의 이유로 저수준 프리미티브 (예 : 기본 이중 체크 잠금 패턴 )를 원합니다. 새로운 표준은 뮤텍스 및 조건 변수와 같은 상위 가제트를 제공하며 원자 유형 및 다양한 메모리 장벽과 같은 저수준 가젯도 제공합니다. 이제 표준에 의해 지정된 언어로 정교하고 고성능의 동시 루틴을 작성할 수 있으며, 현재 시스템과 미래의 시스템 모두에서 코드가 변경되지 않고 컴파일되고 실행될 수 있습니다.

솔직히 말해서, 전문가이고 심각한 저수준 코드로 작업하지 않는 한, 뮤텍스와 조건 변수를 고수해야합니다. 그것이 내가하려는 의도입니다.

이 내용에 대한 자세한 내용은 이 블로그 게시물을 참조하십시오.





c++ design-patterns idioms factory-method