在C或C ++析构函数中模拟GO语言延迟的实用方法是什么?

时间:2018-01-05 16:50:37

标签: c++ c go

简而言之:它是C问题中的智能指针。原因:嵌入式编程需要确保如果使用复杂算法,那么在开发人员方面只需要很少的努力就可以进行适当的解除分配。

我最喜欢的C ++功能是能够执行在堆栈上分配的对象的正确释放,并且超出范围。 GO语言延迟提供相同的功能,它在精神上与C有点接近。

GO defer将是用C做事的理想方式。是否有实用的方法来添加这些功能?

这样做的目的是简化跟踪对象超出范围的时间和地点。这是一个简单的例子:

struct MyDataType *data = malloc(sizeof(struct MyDataType));
defer(data, deallocator);
if (condition) {
    // dallocator(data) is called automatically
    return;
}
// do something
if (irrelevant) {
    struct DT *localScope = malloc(...);
    defer(localScope, deallocator);
    // deallocator(localScope) is called when we exit this scope
}
struct OtherType *data2 = malloc(...);
defer(data2, deallocator);
if (someOtherCondition) {
    // dallocator(data) and deallocator(data2) are called in the order added
    return;
}

在其他语言中,我可以在代码块中创建一个匿名函数,将其分配给变量并在每次返回前手动执行。这至少是部分解决方案。在GO语言中,延迟函数可以链接。在C中使用匿名函数进行手动链接是容易出错且不切实际的。

谢谢

6 个答案:

答案 0 :(得分:1)

C没有析构函数(除非您考虑GCC特定的variable attribute cleanup,这很奇怪且很少使用;另请注意GCC function attribute destructor其他语言,特别是C ++,称为析构函数)。 C ++有它们。并且 C& C ++是非常不同的语言

在C ++ 11中,您可以使用std::vector std::function初始化std::initialized_listlambda expressions来定义您的类(并且可能会动态增加一些push_back)。然后它的析构函数可以模仿Go的defer - ed语句。但这不是惯用语。

Go推迟发表声明,他们在Go中是惯用的。

我建议坚持使用编程语言的习语。

(换句话说:在用C ++编码时不要在Go中思考)

您还可以在应用程序中嵌入一些解​​释器(例如LuaGuile)。您可能还会了解有关garbage collection技术和概念的更多信息,并在您的软件中使用它们(换句话说,使用特定的GC设计您的应用程序)。

  

原因:嵌入式编程并且需要确保如果使用复杂算法,那么在开发人员方面花费很少的精力就会发生正确的解除分配。

您可以使用基于竞技场的分配技术,并在合适的时候取消分配竞技场......当您考虑这一点时,它类似于复制GC技术。

也许你梦想有一些homoiconic语言,它有一个适合元编程的强大宏系统。然后看看Common Lisp。

答案 1 :(得分:1)

在C ++中,我见过遵循RAII模式的“基于堆栈的类”。你可以创建一个可以接受任意函数或lambda的通用Defer类(或结构)。

例如:

#include <cstddef>
#include <functional>
#include <iostream>
#include <string>

using std::cout;
using std::endl;
using std::function;
using std::string;

struct Defer {
  function<void()> action;
  Defer(function<void()> doLater) : action{doLater} {}
  ~Defer() {
    action();
  }
};

void Subroutine(int i) {
  Defer defer1([]() { cout << "Phase 1 done." << endl; });
  if (i == 1) return;
  char const* p = new char[100];
  Defer defer2([p]() { delete[] p; cout << "Phase 2 done, and p deallocated." << endl; });
  if (i == 2) return;
  string s = "something";
  Defer defer3([&s]() { s = ""; cout << "Phase 3 done, and s set to empty string." << endl; });
}

int main() {
  cout << "Call Subroutine(1)." << endl;
  Subroutine(1);
  cout << "Call Subroutine(2)." << endl;
  Subroutine(2);
  cout << "Call Subroutine(3)." << endl;
  Subroutine(3);
  return EXIT_SUCCESS;
}

答案 2 :(得分:1)

我几天前在golang中实现了一个非常简单的事情,比如defer

与golang不同的唯一一个行为是我的defer在你抛出异常时不会被执行但根本没有捕获它。另一个区别是它不能接受像golang这样的多个参数的函数,但是我们可以用lambda捕获局部变量来处理它。

实施在这里。

class _Defer {
    std::function<void()> __callback;
  public:
    _Defer(_Defer &&);
    ~_Defer();

    template <typename T>
        _Defer(T &&);
};

_Defer::_Defer(_Defer &&__that)
        : __callback{std::forward<std::function<void()>>(__that.__callback)} {
}

template <typename T>
_Defer::_Defer(T &&__callback)
        : __callback{
            static_cast<std::function<void()>>(std::forward<T>(__callback))
        } {
    static_assert(std::is_convertible<T, std::function<void()>>::value,
        "Cannot be convert to std::function<void()>.");
}

_Defer::~_Defer() {
    this->__callback();
}

然后我定义了一些宏来使我的defer像C ++中的关键字一样(只是为了好玩)

#define __defer_concatenate(__lhs, __rhs) \
    __lhs##__rhs

#define __defer_declarator(__id) \
    if (0);  /* You may forgot a `;' or deferred outside of a scope. */ \
    _Defer __defer_concatenate(__defer, __id) = 

#define defer \
    __defer_declarator(__LINE__)

if (0);用于防止将函数推迟到作用域之外。然后我们可以在golang中使用defer

#include <iostream>

void foo() {
    std::cout << "foo" << std::endl;
}

int main() {
    defer []() {
        std::cout << "bar" << std::endl;
    };
    defer foo;
}

这将打印

foo
bar

到屏幕。

答案 3 :(得分:1)

许多不同的答案,但没有说一些有趣的细节。

  1. 当然,C ++的析构函数非常强大,应该经常使用。有时一些聪明的指针可以帮助你。但是最接近延迟的机制是ON_BLOCK_EXIT / ON_BLOCK_EXIT_OBJ(参见http://http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758)。别忘了阅读ByRef。
  2. C ++和go之间的一个重要区别是调用deffered时。在C ++中,当您的程序离开作用域时,在哪里创建。但是当你的程序离开功能时。这意味着,这段代码根本不起作用:

    for i:=0; i < 10; i++ {
      mutex.Lock()
      defer mutex.Unlock()
      /* do something under the mutex */
    }
    
  3. 当然C不假装面向对象,因此根本没有析构函数。它有助于代码的大量可读性,因为您知道X行的程序只执行该行中的编写。与C ++相反,每个结束大括号都会导致调用几十个析构函数。

  4. 在C中你可以使用仇恨陈述转到。不要将它用于其他任何事情,但在函数调用goto cleanup的末尾从许多地方获得清理标签是切实可行的。更复杂的是,当您想要发布多个资源时,需要更多的清理。比你的功能完成

    cleanup_file:
       fclose(f);
    cleanup_mutex:
       pthread_mutex_unlock(mutex);
       return ret;
    }
    

答案 4 :(得分:0)

此实现避免了动态分配以及此处显示的其他实现的大多数限制

#include<type_traits>
#include<utility>

template<typename F>
struct deferred
{
    std::decay_t<F> f;
    template<typename G>
    deferred(G&& g) : f{std::forward<G>(g)} {}
    ~deferred() { f(); }
};

template<typename G>
deferred(G&&) -> deferred<G>;

#define CAT_(x, y) x##y
#define CAT(x, y) CAT_(x, y)
#define ANONYMOUS_VAR(x) CAT(x, __LINE__)
#define DEFER deferred ANONYMOUS_VAR(defer_variable) = [&]

并像

一样使用它
#include<iostream>

int main()
{
    DEFER {
        std::cout << "world!\n";
    };
    std::cout << "Hello ";
}

现在,是否允许DEFER中的例外是一种接近哲学的设计选择,我会将其留给Andrei填写详细信息。

请注意,C ++中的所有这些延迟功能必须绑定到声明它的范围,而不是绑定到功能的Go&#39> >声明它。

答案 5 :(得分:0)

  

GO defer将是用C做事的理想方式。是否有实用的方法来添加这些功能?

     

这样做的目的是简化跟踪对象超出范围的时间和地点。

C没有任何内置机制,可以在对象的生命周期结束时自动调用任何类型的行为。对象本身不再存在,它占用的任何内存都可以重用,但是没有用于执行代码的关联钩子。

对于某些类型的对象,这本身就完全令人满意 - 那些值不会引用具有已分配存储持续时间的其他对象的对象也需要清理。特别是,如果示例中的struct MyDataType是这样的类型,那么通过将实例声明为自动变量而不是动态分配它们,可以免费自动清理:

void foo(void) {
    // not a pointer:
    struct MyDataType data /* = initializer */;
    // ...
    /* The memory (directly) reserved for 'data' is released */
}

对于在其生命周期结束时需要注意的对象,通常是代码样式和约定,以确保您知道何时清理。例如,它有助于将所有变量声明在包含它们的最内层块的顶部,尽管C本身并不需要这样做。它还可以帮助构建代码,以便对于需要自定义清理的每个对象,可以在其生命周期内执行的所有代码路径在该生命周期结束时收敛。

我自己,作为个人最佳实践的问题,我总是在编写声明时尝试编写给定对象所需的任何清理代码。

  

在其他语言中,我可以在代码块中创建一个匿名函数,将其分配给变量并在每次返回前手动执行。这至少是部分解决方案。在GO语言中,延迟函数可以链接。在C中使用匿名函数手动链接是容易出错且不切实际的

C既没有匿名函数也没有嵌套函数。但是,为需要清理的数据类型编写(命名)清理函数通常是有意义的。这些类似于C ++析构函数,但您必须手动调用它们。

底线是许多C ++范例,例如智能指针和依赖于它们的编码实践,根本不适用于C.你需要不同的方法,它们存在,但是将大量现有的C ++代码转换为惯用语C是一项非常重要的事业。