배열과 함께 unique_ptr을 사용합니까?


238

std::unique_ptr 예를 들어 배열을 지원합니다.

std::unique_ptr<int[]> p(new int[10]);

그러나 필요합니까? 아마도 사용하는 것이 더 편리하다 std::vectorstd::array.

그 구조에 대한 사용을 찾으십니까?


6
완벽을 기하기 위해 나는 제안이 없다고 std::shared_ptr<T[]>지적해야하지만, 누군가가 제안서를 작성하는 데 방해가된다면 C ++ 14에있을 것이고 아마도있을 것입니다. 그동안 항상 boost::shared_array있습니다.
가명

13
std::shared_ptr<T []>는 이제 C ++ 17에 있습니다.
陳 力

컴퓨터에서 여러 가지 방법으로 작업을 수행 할 수 있습니다. 이 구문은 배열을 대상으로하는 방법을 정확히 알고 있으면 컨테이너 작업의 오버 헤드를 없애기 때문에 특히 핫 경로에서 특히 유용합니다. 또한 연속 스토리지에 대한 의심없이 문자 배열을 만듭니다.
kevr

답변:


256

일부 사람들은 std::vector할당자를 사용 하더라도 고급 기능을 사용할 수 없습니다 . 어떤 사람들은 동적 크기의 배열을 필요로 std::array합니다. 그리고 어떤 사람들은 배열을 반환하는 것으로 알려진 다른 코드에서 배열을 얻습니다. 그 코드는 vector또는 다른 것을 반환하기 위해 다시 작성되지 않습니다 .

을 허용 unique_ptr<T[]>하면 해당 요구 를 처리 할 수 있습니다.

요컨대, 필요할unique_ptr<T[]> 때 사용 합니다. 대안이 단순히 효과가 없을 때. 최후의 수단입니다.


27
@NoSenseEtAl : "일부 사람들은 그렇게 할 수 없습니다"의 어떤 부분이 당신을 회피하는지 잘 모르겠습니다. 일부 프로젝트에는 매우 구체적인 요구 사항이 있으며 그 중 "사용하지 않을 수도 있습니다 vector". 이러한 요구 사항이 합당한 요구 사항인지 아닌지를 주장 할 수는 있지만 요구 사항 이 존재 한다는 것을 부인할 수는 없습니다 .
Nicol Bolas

21
세상에서 누군가가 사용할 수 std::vector있다면 누군가가 사용할 수없는 이유는 없습니다 std::unique_ptr.
Miles Rout

66
vector를 사용하지 않는 이유는 다음과 같습니다. sizeof (std :: vector <char>) == 24; sizeof (std :: unique_ptr <char []>) == 8
Arvid

13
@DanNissenbaum이 프로젝트는 존재합니다. 항공 또는 국방과 같이 매우 엄격한 조사를 받고있는 일부 산업에서는 표준 라이브러리가 규제를 벗어난 모든 기관에 적합하다는 것을 확인하고 증명하기가 어렵 기 때문에 제한이 없습니다. 당신은 표준 라이브러리가 잘 테스트되었고 나는 당신에게 동의 할 것이라고 주장 할 수도 있지만 당신과 나는 규칙을 만들지 않습니다.
Emily L.

16
@DanNissenbaum 또한 일부 하드 실시간 시스템은 시스템 호출로 인한 지연이 이론적으로 제한되지 않고 프로그램의 실시간 동작을 입증 할 수 없으므로 동적 메모리 할당을 전혀 사용할 수 없습니다. 또는 경계가 너무 커서 WCET 제한을 위반할 수 있습니다. 여기서는 적용되지 않지만 unique_ptr둘 중 하나를 사용 하지는 않지만 그러한 종류의 프로젝트는 실제로 존재합니다.
Emily L.

124

트레이드 오프가 있으며 원하는 것과 일치하는 솔루션을 선택합니다. 내 머리 꼭대기에서 :

초기 크기

  • vector그리고 unique_ptr<T[]>크기는 실행 시간에 지정할 수 있습니다
  • array 컴파일시 크기 만 지정할 수 있습니다.

크기 조정

  • arrayunique_ptr<T[]>크기 조정 허용하지 않습니다
  • vector 않습니다

저장

  • vectorunique_ptr<T[]>(일반적 힙) 객체 외부에 데이터를 저장할
  • array 객체에 데이터를 직접 저장

사자

  • array그리고 vector복사를 허용
  • unique_ptr<T[]> 복사를 허용하지 않습니다

스왑 / 이동

  • vectorunique_ptr<T[]>O (1)이 시간 swap및 이동 동작을
  • arrayO (n) 시간 swap및 이동 연산이 있으며 여기서 n은 배열의 요소 수입니다.

포인터 / 참조 / 반복자 무효화

  • array 객체가 활성화되어있는 동안에도 포인터, 참조 및 반복자가 무효화되지 않도록합니다. swap()
  • unique_ptr<T[]>반복자가 없습니다. 포인터와 참조는 swap()객체가 활성화되어있는 동안에 만 무효화됩니다 . 스와핑 한 후에는 포인터가 스왑 한 배열을 가리 키므로 여전히 그런 의미에서 "유효"합니다.
  • vector 재 할당에서 포인터, 참조 및 반복자를 무효화 할 수 있습니다 (재 할당이 특정 작업에서만 발생할 수 있음을 보장합니다).

개념 및 알고리즘과의 호환성

  • array그리고 vector둘 다 컨테이너입니다
  • unique_ptr<T[]> 컨테이너가 아닙니다

정책 기반 설계로 리팩토링 할 수있는 기회 인 것 같습니다.


1
포인터 무효화 의 맥락에서 당신이 무엇을 의미하는지 잘 모르겠습니다 . 이것은 객체 자체에 대한 포인터 또는 요소에 대한 포인터에 관한 것입니까? 또는 다른 것? 벡터에서 얻지 못한 배열에서 어떤 종류의 보증을 얻습니까?
jogojapan

3
반복자, 포인터 또는의 요소에 대한 참조가 있다고 가정하십시오 vector. 그런 다음 vector재 할당 하도록 크기 나 용량을 늘 립니다. 그런 다음 반복자, 포인터 또는 참조가 더 이상의 해당 요소를 가리 키지 않습니다 vector. 이것이 "무효"라는 의미입니다. array"재 할당"이 없기 때문에이 문제가 발생하지 않습니다 . 실제로, 나는 방금 그것에 대한 세부 사항을 보았고 그것을 맞게 편집했습니다.
가명

1
배열에 재 할당하거나 unique_ptr<T[]>재 할당이 없기 때문에 무효화 할 수 없습니다 . 그러나 물론 배열이 범위를 벗어날 때 특정 요소에 대한 포인터는 여전히 유효하지 않습니다.
jogojapan

예, 개체가 더 이상 존재하지 않으면 모든 베팅이 해제됩니다.
가명

1
@rubenvb 물론 가능하지만 범위 기반 for 루프를 직접 사용할 수는 없습니다. 또한 normal과 달리 배열의 요소를 올바르게 파괴하려면 T[]크기 (또는 동등한 정보)가 어딘가에 매달려 있어야합니다 operator delete[]. 프로그래머가 액세스 할 수 있다면 좋을 것입니다.
가명

73

a를 사용하는 한 가지 이유 는 배열 unique_ptr값을 초기화 하는 런타임 비용을 지불하고 싶지 않기 때문 입니다.

std::vector<char> vec(1000000); // allocates AND value-initializes 1000000 chars

std::unique_ptr<char[]> p(new char[1000000]); // allocates storage for 1000000 chars

std::vector생성자와 std::vector::resize()초기화 값 것이다 T- 그러나 new경우는 그렇게하지 않을 것이다 T포드입니다.

C ++ 11 및 std :: vector 생성자의 Value-Initialized Objects 참조

참고 vector::reserve여기에 대안이 아니다는 : 표준 : : 벡터 후에 원시 포인터에 액세스 :: 예비 안전?

그것은 C 프로그래머가 선택할 수 같은 이유 malloc이상 calloc.


그러나이 이유만이 유일한 해결책아닙니다 .
Ruslan

@Ruslan 연결된 솔루션에서 동적 배열의 요소는 여전히 값으로 초기화되지만 값 초기화는 아무 것도 수행하지 않습니다. 나는 코드로 1000000 번 아무것도 할 수 없다는 것을 깨닫지 못하는 옵티마이 저는 가치가 없지만,이 최적화에 전혀 의존하지 않는 것을 선호 할 것입니다.
Marc van Leeuwen

그러나 또 다른 가능성은 유형의 구성 과 객체의 파괴 를 피하는 커스텀 할당 자 에게 제공하는 std::vector것 입니다. std::is_trivially_default_constructiblestd::is_trivially_destructible
Walter

또한 std::unique_ptr많은 std::vector구현 과 달리 바운드 검사를 제공하지 않습니다 .
diapir

@diapir 그것은 구현에 관한 것이 아니다 : std::vector에서 경계를 검사하기 위해 표준에 필요하다 .at(). 일부 구현에는 체크인 할 디버그 모드가 .operator[]있지만 좋은 휴대용 코드를 작성하는 데 쓸모없는 것으로 생각합니다.
underscore_d

30

std::vector동안, 주변에 복사 할 수 있습니다 unique_ptr<int[]>배열의 고유 한 소유권을 표현 할 수 있습니다. std::array반면에, 컴파일 타임에 크기를 결정해야하므로 일부 상황에서는 불가능할 수 있습니다.


2
무언가 복사 할 수 있다고해서 반드시 복사해야하는 것은 아닙니다.
Nicol Bolas

4
@NicolBolas : 이해가 안 돼요. 같은 이유로을 unique_ptr대신 사용 하는 것을 막고 싶을 수도 있습니다 shared_ptr. 뭔가 빠졌습니까?
Andy Prowl

4
unique_ptr우발적 인 오용을 방지하는 것 이상을 수행합니다. 또한보다 작고 오버 헤드가 낮습니다 shared_ptr. 요점은 "오용"을 방지하는 클래스에 의미를 갖는 것이 좋지만 특정 유형을 사용해야하는 유일한 이유는 아니라는 점입니다. 그리고 크기 가 있다는 사실 외에 다른 이유가없는 경우 vector보다 어레이 스토리지로 훨씬 더 유용 합니다 . unique_ptr<T[]>
Nicol Bolas

3
나는 그 요점을 명확하게 생각했다 : 그것보다 특정한 유형을 사용해야하는 다른 이유 들이있다. 그냥 좋아하는 이유가 같은 vector이상 unique_ptr<T[]>대신 말하는, "당신이 그것을 복사 할 수 없습니다"따라서 선택 가능한, unique_ptr<T[]>당신이 사본을하지 않으려는 경우가. 누군가가 잘못한 일을 막는 것이 반드시 수업을 선택하는 가장 중요한 이유는 아닙니다.
Nicol Bolas

8
std::vectora보다 오버 헤드가 더 많으며 std::unique_ptr~ 1 대신 ~ 3 포인터를 사용합니다. std::unique_ptr복사 구성을 차단하지만 이동 구성을 활성화합니다. 의미 적으로 작업중인 데이터를 이동 만 할 수 있지만 복사 할 수없는 class경우 데이터 가 포함 된 데이터를 감염시킵니다 . 유효하지 않은 데이터에 대한 조작이 실제로 컨테이너 클래스를 악화시키고 "사용하지 마십시오"라고 모든 죄를 씻어 내지는 않습니다. std::vector수동으로 비활성화하는 클래스 에 모든 인스턴스를 넣어야 move하는 것은 골치 아픈 일입니다. std::unique_ptr<std::array>가 있습니다 size.
Yakk-Adam Nevraumont

22

Scott Meyers는 Effective Modern C ++에서 다음과 같이 말합니다.

의 존재 std::unique_ptr배열이 있기 때문에, 당신 만 지적 관심을해야한다 std::array, std::vector,std::string 거의 항상 원시 배열보다 더 나은 데이터 구조의 선택입니다. 내가 std::unique_ptr<T[]>이해할 수있는 유일한 상황에 대해서는 소유권이 있다고 가정하는 힙 배열에 대한 원시 포인터를 반환하는 C와 같은 API를 사용하는 경우가 될 것입니다.

Charles Salvia의 대답은 관련이 있다고 생각합니다 std::unique_ptr<T[]>. 컴파일시 크기를 알 수없는 빈 배열을 초기화하는 유일한 방법입니다. Scott Meyers는 이러한 사용 동기에 대해 무엇을 말해야 std::unique_ptr<T[]>합니까?


4
크기가 고정되었지만 컴파일 타임에 알 수없는 버퍼 및 / 또는 복사를 허용하지 않는 버퍼와 같은 몇 가지 유스 케이스를 상상하지 않은 것처럼 들립니다. vector stackoverflow.com/a/24852984/2436175보다 선호하는 가능한 이유도 있습니다 .
안토니오

17

반대로 std::vector하고 std::array, std::unique_ptrNULL 포인터를 소유 할 수 있습니다.
이것은 배열 또는 NULL을 기대하는 C API로 작업 할 때 유용합니다.

void legacy_func(const int *array_or_null);

void some_func() {    
    std::unique_ptr<int[]> ptr;
    if (some_condition) {
        ptr.reset(new int[10]);
    }

    legacy_func(ptr.get());
}

10

내가 사용했던 unique_ptr<char[]>게임 엔진에 사용되는 사전 할당 된 메모리 풀을 구현 할 수 있습니다. 아이디어는 충돌 요청 결과 및 각 프레임에서 메모리를 할당 / 해제하지 않고도 입자 물리와 같은 다른 것들을 반환하기 위해 동적 할당 대신 사용되는 미리 할당 된 메모리 풀을 제공하는 것입니다. 파괴 논리가 필요하지 않은 (메모리 할당 해제 만) 제한된 수명 시간 (일반적으로 1, 2 또는 3 프레임)으로 객체를 할당하기 위해 메모리 풀이 필요한 이러한 종류의 시나리오에는 매우 편리합니다.


9

일반적인 일부 패턴은 일부 Windows Win32 API 호출 에서 찾을 수 있습니다 std::unique_ptr<T[]>. 예를 들어 일부 Win32 API를 호출 할 때 출력 버퍼의 크기를 정확히 알지 못하는 경우 (예 : 내부에 데이터를 쓰는 경우) 해당 버퍼) :

// Buffer dynamically allocated by the caller, and filled by some Win32 API function.
// (Allocation will be made inside the 'while' loop below.)
std::unique_ptr<BYTE[]> buffer;

// Buffer length, in bytes.
// Initialize with some initial length that you expect to succeed at the first API call.
UINT32 bufferLength = /* ... */;

LONG returnCode = ERROR_INSUFFICIENT_BUFFER;
while (returnCode == ERROR_INSUFFICIENT_BUFFER)
{
    // Allocate buffer of specified length
    buffer.reset( BYTE[bufferLength] );
    //        
    // Or, in C++14, could use make_unique() instead, e.g.
    //
    // buffer = std::make_unique<BYTE[]>(bufferLength);
    //

    //
    // Call some Win32 API.
    //
    // If the size of the buffer (stored in 'bufferLength') is not big enough,
    // the API will return ERROR_INSUFFICIENT_BUFFER, and the required size
    // in the [in, out] parameter 'bufferLength'.
    // In that case, there will be another try in the next loop iteration
    // (with the allocation of a bigger buffer).
    //
    // Else, we'll exit the while loop body, and there will be either a failure
    // different from ERROR_INSUFFICIENT_BUFFER, or the call will be successful
    // and the required information will be available in the buffer.
    //
    returnCode = ::SomeApiCall(inParam1, inParam2, inParam3, 
                               &bufferLength, // size of output buffer
                               buffer.get(),  // output buffer pointer
                               &outParam1, &outParam2);
}

if (Failed(returnCode))
{
    // Handle failure, or throw exception, etc.
    ...
}

// All right!
// Do some processing with the returned information...
...

std::vector<char>이 경우에 사용할 수 있습니다 .
Arthur Tacca

@ArthurTacca-... 버퍼의 모든 문자를 하나씩 0으로 초기화하는 컴파일러가 마음에 들지 않으면.
TED

9

std::unique_ptr<bool[]>HDF5 라이브러리 (이진 데이터 저장을위한 라이브러리, 과학에서 많이 사용됨)에있는 을 사용해야하는 경우에 직면했습니다 . 일부 컴파일러 (필자의 경우 Visual Studio 2015) 는 압축에std::vector<bool> 신경 쓰지 않는 HDF5와 같은 재앙 인 (모든 바이트에 8 부울을 사용하여) 압축을 제공합니다. 를 사용 std::vector<bool>하면 HDF5는 압축으로 인해 결국 가비지를 읽었습니다.

std::vector작동하지 않는 경우 누가 구조를 위해 있었는지 추측 하고 동적 배열을 깨끗하게 할당해야합니까? :-)


9

요컨대, 메모리 효율성이 가장 뛰어납니다.

A std::string는 포인터, 길이 및 "짧은 문자열 최적화"버퍼와 함께 제공됩니다. 그러나 내 상황은 거의 항상 비어있는 문자열을 수십만 개의 구조로 저장해야한다는 것입니다. C에서는을 사용 char *하고 대부분의 경우 null이됩니다. char *소멸자가 없으며 자체 삭제를 모르는 것을 제외하고 C ++에서도 작동 합니다. 반대로 a std::unique_ptr<char[]>는 범위를 벗어날 때 자체를 삭제합니다. 빈칸 std::string은 32 바이트를 차지하지만 빈칸 std::unique_ptr<char[]>은 8 바이트를 차지합니다.

가장 큰 단점은 줄의 길이를 알고 싶을 때마다 전화해야한다는 strlen것입니다.


3

장치에서 메모리를 할당 할 때 GPU에서 CUDA 프로그래밍의 경우를 사용하는 vector대신 "사용해야한다고 생각하는 사람들에게 대답하려면 unique_ptr포인터 배열 (with cudaMalloc) 을 사용해야합니다 . 그런 다음 호스트에서이 데이터를 검색 할 때 포인터를 다시 찾아야하며 unique_ptr포인터를 쉽게 처리 할 수 ​​있습니다. 로 변환 double*하는 데 드는 추가 비용 vector<double>은 불필요하며 성능이 저하됩니다.


3

std::unique_ptr<T[]>지금까지 응답에서 언급되지 않은 을 허용하고 사용하는 한 가지 추가 이유 : 배열 요소 유형을 전달할 수 있습니다.

이는 #include빌드 성능을 최적화하기 위해 헤더 의 체인 문 을 최소화하려는 경우에 유용 합니다.

예를 들어-

myclass.h :

class ALargeAndComplicatedClassWithLotsOfDependencies;

class MyClass {
   ...
private:
   std::unique_ptr<ALargeAndComplicatedClassWithLotsOfDependencies[]> m_InternalArray;
};

myclass.cpp :

#include "myclass.h"
#include "ALargeAndComplicatedClassWithLotsOfDependencies.h"

// MyClass implementation goes here

위의 코드 구조를 #include "myclass.h"사용 MyClass하면에 필요한 내부 구현 종속성을 포함하지 않고도 누구나 사용할 수 있습니다 MyClass::m_InternalArray.

m_InternalArray대신 각각 std::array<ALargeAndComplicatedClassWithLotsOfDependencies>, 또는 로 선언 된 경우 std::vector<...>결과는 불완전한 유형의 사용을 시도하며 이는 컴파일 타임 오류입니다.


이 특정 유스 케이스의 경우, Pimpl 패턴이 의존성을 깨뜨 리도록 선택합니다. 개인적으로 만 사용되는 경우 클래스 메소드가 구현 될 때까지 정의를 연기 할 수 있습니다. 공개적으로 사용되는 경우 클래스 사용자는 이미에 대한 구체적인 지식을 가지고 있어야합니다 class ALargeAndComplicatedClassWithLotsOfDependencies. 따라서 논리적으로 그러한 시나리오를 실행해서는 안됩니다.

3

나는 받아 들여진 대답의 정신에 충분히 동의하지 않습니다. "최후의 수단"? 그것과는 거리가 멀다!

내가 보는 방법은 C와 다른 C ++에 비해 C ++의 가장 강력한 기능 중 하나는 제약 조건을 표현하여 컴파일 타임에 확인하고 실수로 오용되는 것을 방지 할 수 있다는 것입니다. 따라서 구조를 설계 할 때 어떤 작업을 허용 해야하는지 스스로에게 물어보십시오. 다른 모든 사용은 금지되어야하며, 이러한 제한을 정적으로 (컴파일 시간에) 구현하여 잘못 사용하면 컴파일이 실패하는 것이 가장 좋습니다.

따라서 배열이 필요할 때 다음 질문에 대한 대답은 동작을 지정합니다. 2. 스택에 어레이를 할당 할 수 있습니까?

그리고 답을 바탕으로, 이것은 그러한 배열에 가장 적합한 데이터 구조로 간주됩니다.

       Dynamic     |   Runtime static   |         Static
Stack std::vector      unique_ptr<T[]>          std::array
Heap  std::vector      unique_ptr<T[]>     unique_ptr<std::array>

그래, 내 생각에는 unique_ptr<std::array> 또한 고려해야 하며, 최후의 수단이 아니다. 알고리즘에 가장 적합한 것을 생각하십시오.

이 모든 것은 데이터 배열에 대한 원시 포인터 ( vector.data()/ array.data()/ uniquePtr.get()) 를 통해 일반 C API와 호환됩니다 .

PS 위의 고려 사항 외에도 소유권 중 하나가 있습니다. std::arraystd::vector동시에, 값 의미를 (복사 한 값으로 전달하기위한 네이티브 지원이)가 unique_ptr<T[]>전용 (시행한다 단일 소유권을) 이동할 수 있습니다. 다른 시나리오에서 유용 할 수 있습니다. 반대로, 일반 정적 배열 ( int[N]) 및 일반 동적 배열 ( new int[10])은 둘 다를 제공하지 않으므로 가능하면 피해야합니다. 대부분의 경우 가능해야합니다. 이것으로 충분하지 않은 경우 일반 동적 배열은 크기를 쿼리 할 수있는 방법을 제공하지 않으므로 메모리 손상 및 보안 허점에 대한 추가 기회가 제공됩니다.


2

해치의 다른 쪽에서 "잡힌"후 수명이 측정되는 기존 API (창 메시지 또는 스레딩 관련 콜백 매개 변수)를 통해 단일 포인터 만 찌를 때 가능한 가장 올바른 대답 일 수 있습니다. 그러나 이것은 호출 코드와 관련이 없습니다.

unique_ptr<byte[]> data = get_some_data();

threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); },
                      data.release());

우리 모두는 우리에게 좋은 일을 원합니다. 다른 때는 C ++입니다.


2

unique_ptr<char[]>C의 성능과 C ++의 편리함을 원하는 곳에서 사용할 수 있습니다. 수백만 개 (아직 신뢰할 수없는 경우 수십억 개)의 문자열을 조작해야한다고 생각하십시오. 각각을 개별 string또는 vector<char>오브젝트 에 저장 하면 메모리 (힙) 관리 루틴에 재앙이됩니다. 특히 다른 문자열을 여러 번 할당하고 삭제해야하는 경우.

그러나 많은 문자열을 저장하기 위해 단일 버퍼를 할당 할 수 있습니다. char* buffer = (char*)malloc(total_size);명백한 이유로 마음 에 들지 않을 것 입니다 (명확하지 않은 경우 "스마트 ptr을 사용하는 이유"를 검색하십시오). 당신은 오히려 좋아합니다unique_ptr<char[]> buffer(new char[total_size]);

비유로, 동일한 성능 및 편의성 고려 사항이 비 char데이터에 적용됩니다 (수백만 개의 벡터 / 행렬 / 개체 고려).


그들 모두를 하나의 큰 것으로 넣지 않았 vector<char>습니까? 대답은 버퍼를 만들 때 0으로 초기화되고을 사용하는 경우에는 초기화되지 않기 때문입니다 unique_ptr<char[]>. 그러나이 핵심 덩어리는 귀하의 답변에서 누락되었습니다.
Arthur Tacca

2
  • 이진 호환성을 위해 포인터 만 포함하는 구조가 필요합니다.
  • 할당 된 메모리를 반환하는 API와 인터페이스해야합니다. new[]
  • std::vector예를 들어 부주의 한 프로그래머가 실수로 사본을 소개하지 않도록하기 위해 회사 나 프로젝트에을 사용하는 것에 대한 일반적인 규칙이 있습니다.
  • 이 경우 부주의 한 프로그래머가 실수로 사본을 소개하지 않도록합니다.

포인터를 사용하여 롤링하는 것보다 C ++ 컨테이너를 선호하는 것이 일반적 규칙입니다. 일반적인 규칙입니다. 예외가 있습니다. 더있다; 이것들은 단지 예일뿐입니다.


0

복사 할 수없는 동적 객체 배열이 필요한 경우, 배열에 대한 스마트 포인터가 필요합니다. 예를 들어 원자 배열이 필요한 경우 어떻게해야합니까?

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.