C ++에서 VLA (스택 할당, 가변 크기)처럼 작동하고 함수에서 반환 될 수도 있고 선형 할당 자처럼 메모리를 낭비하지 않는 유형을 생성 할 수 있는지 궁금합니다.
다음과 같이 사용할 수 있습니다.
// Fibonacci sequence
stack_array<int> fib(size_t n) {
stack_array<int> f(n);
f[0] = 0;
f[1] = 1;
for(size_t i=2;i<n;++i) {
f[i] = f[i-1] + f[i-2];
}
return f;
}
std::vector
힙 할당과 함께 가변 크기의 항목 (일반적으로 사용 )을 반환하기 때문에 유용합니다 . 힙 할당이 허용되지 않는 실시간 컨텍스트에서이 작업을 수행 할 수도 있습니다. 오디오 버퍼에 대한 기능적 스타일 프로그래밍. 하지만 대체로 이것은 무엇이 가능한지 알아보기위한 지적 연습입니다.
std::allocator
선형 할당을 수행하여 실시간 제약 조건을 충족하고 사소하지 않은 유형을 쉽게 지원하는를 작성할 수 있다는 것을 알고 있지만 이것은 메모리 낭비의 단점이 있습니다. 더 잘할 수 있을지 궁금합니다.
이를 더 구체적으로 만들기 위해 다음이 있다고 가정합니다.
thread_local arena myArena; // I don't want to have to pass this around!
std::vector<float> f() {
return std::vector<float, arena>(64, myArena);
}
std::vector<float> g() {
return std::vector<float, arena>(64, myArena);
}
void main() {
{
auto a = f();
}
{
auto b = g();
}
// At this point, we still have 128*sizeof(float) allocated.
// You might say to use multiple arenas, but that might not
// be easy.
}
이제 함수에서 반환하지 않으면 LIFO 생성 / 파괴 순서가 보장된다는 것을 알고 있습니다 (RAII에 필요함). 컨테이너에 제약 조건을 적용하여 LIFO 순서를 보장하면서 함수에서 반환 할 수 있는지 궁금합니다.
여기에 별도의 thread_local
스택을 사용하여 구현하려는 시도가 있지만 구성 및 파괴 순서가 불변을 약간 혼란스럽게 유지하도록 보장하기 때문에 실제로 올바른지 모르겠습니다.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <iostream>
#include <type_traits>
// Big stack.
static size_t stackSize = 1024*1024*10;
static thread_local uint8_t* start = nullptr;
static thread_local uint8_t* stack = nullptr;
// An array with simple stack based allocation.
// Size is fixed on creation.
//
// I'm assuming this is difficult with std::vector
// and a custom allocator because it can grow.
//
// T must be trivial for now
template<class T>
class stack_array {
size_t _size;
T* ptr;
public:
stack_array(size_t size) : _size(size) {
static_assert(std::is_trivial<T>::value);
if(stack == nullptr) {
start = stack = (uint8_t*) malloc(stackSize);
}
ptr = (T*) stack;
stack += size*sizeof(T);
assert(stack < start + stackSize);
printf("ctor %p, stack %d\n", (void*) this, int(stack - start));
}
// I think the copy constructor violates our stack
// nesting property, since the lifetimes overlap.
stack_array(const stack_array&) = delete;
~stack_array() {
stack -= _size*sizeof(T);
printf("dtor %p, stack %d\n", (void*) this, int(stack - start));
}
stack_array& operator=(const stack_array& a) {
printf("assign\n");
assert(_size == a._size);
for(size_t i=0;i<_size;++i) {
ptr[i] = a.ptr[i];
}
return *this;
}
// Is this actually correct?
stack_array(stack_array&& other) {
printf("move\n");
ptr = other.ptr;
_size = other._size;
other._size = 0;
}
T& operator[](size_t i) { assert(i < _size); return ptr[i]; }
const T& operator[](size_t i) const { assert(i < _size); return ptr[i]; }
size_t size() const { return _size; }
};
// Fibonacci sequence
stack_array<int> fib(size_t n) {
stack_array<int> f(n);
f[0] = 0;
f[1] = 1;
for(size_t i=2;i<n;++i) {
f[i] = f[i-1] + f[i-2];
}
return f;
}
template<class T, class F>
void foreach(const stack_array<T> &a, F f) {
for(size_t i=0;i<a.size();++i) {
f(a[i]);
}
}
template<class T, class F>
auto map(const stack_array<T> &a, F f) {
stack_array< decltype(f(T())) > r(a.size());
for(size_t i=0;i<a.size();++i) {
r[i] = f(a[i]);
}
return r;
}
template<class T, class F>
auto filter(const stack_array<T> &a, F f) {
stack_array<int> b(a.size());
size_t n = 0;
for(size_t i=0;i<a.size();++i) {
if(f(a[i])) {
b[n++] = i;
}
}
stack_array<T> r(n);
for(size_t i=0;i<n;++i) {
r[i] = a[b[i]];
}
return r;
}
template<class T> void printArray(const stack_array<T> &a) {
foreach(a, [](T t){ std::cout << t << std::endl;});
}
int main(int argc, char* argv[]) {
{
stack_array<int> a = fib(20);
printArray(a);
printArray(map(a, [](int i) { return i+1; }));
printArray(filter(a, [](int i) { return i%2; }));
stack_array<int> b(a.size());
b = a;
}
assert(stack == start);
}
스택 기반 접근 방식을 간략히 살펴보면 자체 작은 개인 힙에서 작동하는 힙 기반 할당 자에 지나지 않지만 엄격한 제한이있어 할당이 엄격하게 LIFO 순서로 수행되지 않으면 오작동합니다. 이 속도에서는 동일한 할당 메커니즘을 유지하면서 실제로 중요하지 않은 유형을 지원하도록 확장하는 것을 고려할 수 있습니다.
즉, 정상적인 사용 하에서는 전제 조건을 유지하지 않는 것 같습니다. 그때 는 filter
구성 하지만 겉보기에는 오래 지속되는 것 같습니다 (액터 및 dtor에서 "스택"의 비트를 수동으로 중독 및 제거하여 ASAN에서 발견).b
r
r
b
메모리가 너무 빡빡하지 않으면 아레나 할당 자 사용을 고려할 수 있습니다. (사용자에 대한 명시적인 요구 사항으로 또는 참조 계산을 통해) 할당 된 모든 할당보다 오래 지속되는 작은 경기장을 유지할 수 있습니다.
또한 호출자에 스택 기반 배열을 할당하고 포인터 / 참조를 전달하는 것과 같이 값을 반환하는 더 전통적인 방법을 고려할 수도 있습니다. 적어도 오디오 애플리케이션의 경우 고정 길이 버퍼도 적합 할 수 있습니다.
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다