Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/160.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/arduino/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
共享指针是否会中断尾部调用优化? 前言 我正在练习C++,并试图实现不可变列表。 在我的一个测试中,我试图递归地创建一个包含大量值(100万个节点)的列表。所有的值都是常量,所以我不能执行常规循环,这也不够功能,你知道。 测试失败,出现分段故障_C++_Recursion_Shared Ptr_Compiler Optimization_Tail Recursion - Fatal编程技术网

共享指针是否会中断尾部调用优化? 前言 我正在练习C++,并试图实现不可变列表。 在我的一个测试中,我试图递归地创建一个包含大量值(100万个节点)的列表。所有的值都是常量,所以我不能执行常规循环,这也不够功能,你知道。 测试失败,出现分段故障

共享指针是否会中断尾部调用优化? 前言 我正在练习C++,并试图实现不可变列表。 在我的一个测试中,我试图递归地创建一个包含大量值(100万个节点)的列表。所有的值都是常量,所以我不能执行常规循环,这也不够功能,你知道。 测试失败,出现分段故障,c++,recursion,shared-ptr,compiler-optimization,tail-recursion,C++,Recursion,Shared Ptr,Compiler Optimization,Tail Recursion,我的系统是64位Xubuntu 16.04 LTS和Linux 4.4。 我使用g++5.4和clang++3.8编译我的代码,使用--std=c++14-O3标志 源代码 我已经写了一个简单的例子,它展示了这样一种情况,当尾部调用应该很容易优化时,出现了一些错误,出现了分段错误。函数f只需等待amount迭代,然后创建指向singleint的指针并返回它 #include <memory> using std::shared_ptr; shared_ptr<int>

我的系统是64位Xubuntu 16.04 LTS和Linux 4.4。 我使用g++5.4和clang++3.8编译我的代码,使用
--std=c++14-O3
标志

源代码 我已经写了一个简单的例子,它展示了这样一种情况,当尾部调用应该很容易优化时,出现了一些错误,出现了
分段错误
。函数
f
只需等待
amount
迭代,然后创建指向single
int
的指针并返回它

#include <memory>

using std::shared_ptr;

shared_ptr<int> f(unsigned amount) {
    return amount? f(amount - 1) : shared_ptr<int>{new int};
}

int main() {
    return f(1E6) != nullptr;
}
注意 两个编译器都很好地优化了使用常规指针的实现

问题: 在我的例子中,
shared_ptr
真的打破了尾部调用优化吗? 这是编译器的问题还是
共享\u ptr
实现的问题?

答案 简单的回答是:是和否

C++中的共享指针不会中断尾调用优化, 但它使这种递归函数的创建变得复杂,编译器可以将其转换为循环

细节 在递归构造长列表期间避免堆栈溢出 <>我记得,<代码> SyddpPTR>代码>有析构函数,C++有RAII。 正如前面讨论的那样,这使得构造可优化的尾部调用变得更加困难

@KennyOstrom建议使用普通指针来解决这个问题

static const List* insertBulk_(unsigned amount, const List* tail=nullptr) {
    return amount? insertBulk_(amount - 1, new List{tail})
                 : tail;
}
使用以下构造函数

List(const List* tail): tail{tail} {}
List
tail
shared\u ptr
的实例时,tail调用被成功优化

在销毁期间避免堆栈溢出 需要定制销毁策略。 幸运的是,
shared\u ptr
允许我们设置它, 所以我隐藏了
List
的析构函数,使其成为私有的, 并使用此列表进行销毁

static void destroy(const List* list) {
    if (!list) return;

    shared_ptr<const List> tail = list->tail;
    delete list;
    for (; tail && tail.use_count() == 1; tail = tail->tail);
}
避免内存泄漏 在异常情况下,我不会进行适当的清理,因此问题尚未解决。 我想使用
shared\u ptr
,因为它是安全的,但现在我在构建结束之前不会将其用于当前列表头

它需要监视“裸”指针,直到它被包装成共享指针,并在紧急情况下释放它。 让我们将对尾部指针的引用而不是指针本身传递给
insertBulk
。 这将允许最后一个良好的指针在函数外部可见

static const List* insertBulk_(unsigned amount, const List*& tail) {
    if (!amount) {
        const List* result = tail;
        tail = nullptr;
        return result;
    }
    return insertBulk_(amount - 1, tail = new List{tail});
}
然后需要模拟
最后
,以便自动销毁指针,在异常情况下会泄漏指针

static const shared_ptr<const List> insertBulk(unsigned amount) {
    struct TailGuard {
        const List* ptr;
        ~TailGuard() {
            List::destroy(this->ptr);
        }
    } guard{};
    const List* result = insertBulk_(amount, guard.ptr);
    return amount? shared_ptr<const List>{result, List::destroy}
                 : nullptr;
}
源代码的较短版本
列表
没有注释的类,并检入
main
函数

#include <memory>

using std::shared_ptr;

class List {
    private:
        const shared_ptr<const List> tail;

        static const List* insertBulk_(unsigned amount, const List*& tail) {
            if (!amount) {
                const List* result = tail;
                tail = nullptr;
                return result;
            }
            return insertBulk_(amount - 1, tail = new List{tail});
        }
        ~List() {}
    public:
        List(const List* tail): tail{tail, List::destroy} {}
        List(const shared_ptr<const List>& tail): tail{tail} {}
        List(const List&) = delete;
        List() = delete;

        static const shared_ptr<const List> insertBulk(unsigned amount) {
            struct TailGuard {
                const List* ptr;
                ~TailGuard() {
                    List::destroy(this->ptr);
                }
            } guard{};
            const List* result = insertBulk_(amount, guard.ptr);
            return amount? shared_ptr<const List>{result, List::destroy}
                         : nullptr;
        }
        static void destroy(const List* list) {
            if (!list) return;

            shared_ptr<const List> tail = list->tail;
            delete list;

            for (; tail && tail.use_count() == 1; tail = tail->tail);
        }
};
#包括
使用std::shared_ptr;
班级名单{
私人:
const共享_ptr tail;
静态常量列表*插入批量(未签名金额、常量列表*&尾部){
如果(!金额){
常量列表*结果=尾部;
tail=nullptr;
返回结果;
}
返回insertBulk(amount-1,tail=newlist{tail});
}
~List(){}
公众:
List(const List*tail):tail{tail,List::destroy}{}
列表(const shared_ptr&tail):tail{tail}{}
列表(const List&)=删除;
List()=删除;
静态常量共享\u ptr insertBulk(未签名金额){
结构尾护板{
施工清单*ptr;
~TailGuard(){
列表::销毁(本->ptr);
}
}警卫{};
const List*result=insertBulk(金额,guard.ptr);
返回金额?共享{结果,列表::销毁}
:nullptr;
}
静态无效销毁(常量列表*列表){
如果(!list)返回;
共享\u ptr tail=列表->尾部;
删除名单;
对于(;tail&&tail.use_count()==1;tail=tail->tail);
}
};

“但是我想这个例子在使用共享的\u PTR宏时更清楚。”-不是。@NeilButterworth谢谢,我已经写了更简单的例子,看起来你为每个级别构造了一个新的共享\u PTR包装实例。尝试引用?@KennyOstrom是的,引用发挥了作用,因此我更改了示例以更真实地显示问题。为什么使用
共享的ptr
而不是
唯一的ptr
?您是否计划让多个列表共享同一个尾部,因为它们是不可变的?
static const shared_ptr<const List> insertBulk(unsigned amount) {
    struct TailGuard {
        const List* ptr;
        ~TailGuard() {
            List::destroy(this->ptr);
        }
    } guard{};
    const List* result = insertBulk_(amount, guard.ptr);
    return amount? shared_ptr<const List>{result, List::destroy}
                 : nullptr;
}
#include <memory>
#include <cassert>

using std::shared_ptr;

class List {
    private:
        const shared_ptr<const List> tail;

        /**
         * I need a `tail` to be an instance of `shared_ptr`.
         * Separate `List` constructor was created for this purpose.
         * It gets a regular pointer to `tail` and wraps it
         * into shared pointer.
         *
         * The `tail` is a reference to pointer,
         * because `insertBulk`, which called `insertBulk_`,
         * should have an ability to free memory
         * in the case of `insertBulk_` fail
         * to avoid memory leak.
         */
        static const List* insertBulk_(unsigned amount, const List*& tail) {
            if (!amount) {
                const List* result = tail;
                tail = nullptr;
                return result;
            }
            return insertBulk_(amount - 1, tail = new List{tail});
        }
        unsigned size_(unsigned acc=1) const {
            return this->tail? this->tail->size_(acc + 1) : acc;
        }
        /**
         * Destructor needs to be hidden,
         * because it causes stack overflow for long lists.
         * Custom destruction method `destroy` should be invoked first.
         */
        ~List() {}
    public:
        /**
         * List needs custom destruction strategy,
         * because default destructor causes stack overflow
         * in the case of long lists:
         * it will recursively remove its items.
         */
        List(const List* tail): tail{tail, List::destroy} {}
        List(const shared_ptr<const List>& tail): tail{tail} {}
        List(const List&) = delete;
        List() = delete;

        unsigned size() const {
            return this->size_();
        }

        /**
         * Public iterface for private `insertBulk_` method.
         * It wraps `insertBulk_` result into `shared_ptr`
         * with custom destruction function.
         *
         * Also it creates a guard for tail,
         * which will destroy it if something will go wrong.
         * `insertBulk_` should store `tail`,
         * which is not yet wrapped into `shared_ptr`,
         * in the guard, and set it to `nullptr` in the end
         * in order to avoid destruction of successfully created list.
         */
        static const shared_ptr<const List> insertBulk(unsigned amount) {
            struct TailGuard {
                const List* ptr;
                ~TailGuard() {
                    List::destroy(this->ptr);
                }
            } guard{};
            const List* result = insertBulk_(amount, guard.ptr);
            return amount? shared_ptr<const List>{result, List::destroy}
                         : nullptr;
        }
        /**
         * Custom destruction strategy,
         * which should be called in order to delete a list.
         */
        static void destroy(const List* list) {
            if (!list) return;

            shared_ptr<const List> tail = list->tail;
            delete list;

            /**
             * Watching references count allows us to stop,
             * when we reached the node,
             * which is used by another list.
             *
             * Also this prevents long loop of construction and destruction,
             * because destruction calls this function `destroy` again
             * and it will create a lot of redundant entities
             * without `tail.use_count() == 1` condition.
             */
            for (; tail && tail.use_count() == 1; tail = tail->tail);
        }
};

int main() {
    /**
     * Check whether we can create multiple lists.
     */
    const shared_ptr<const List> list{List::insertBulk(1E6)};
    const shared_ptr<const List> longList{List::insertBulk(1E7)};
    /**
     * Check whether we can use a list as a tail for another list.
     */
    const shared_ptr<const List> composedList{new List{list}, List::destroy};
    /**
     * Checking whether creation works well.
     */
    assert(list->size() == 1E6);
    assert(longList->size() == 1E7);
    assert(composedList->size() == 1E6 + 1);
    return 0;
}
#include <memory>

using std::shared_ptr;

class List {
    private:
        const shared_ptr<const List> tail;

        static const List* insertBulk_(unsigned amount, const List*& tail) {
            if (!amount) {
                const List* result = tail;
                tail = nullptr;
                return result;
            }
            return insertBulk_(amount - 1, tail = new List{tail});
        }
        ~List() {}
    public:
        List(const List* tail): tail{tail, List::destroy} {}
        List(const shared_ptr<const List>& tail): tail{tail} {}
        List(const List&) = delete;
        List() = delete;

        static const shared_ptr<const List> insertBulk(unsigned amount) {
            struct TailGuard {
                const List* ptr;
                ~TailGuard() {
                    List::destroy(this->ptr);
                }
            } guard{};
            const List* result = insertBulk_(amount, guard.ptr);
            return amount? shared_ptr<const List>{result, List::destroy}
                         : nullptr;
        }
        static void destroy(const List* list) {
            if (!list) return;

            shared_ptr<const List> tail = list->tail;
            delete list;

            for (; tail && tail.use_count() == 1; tail = tail->tail);
        }
};