首页 > 解决方案 > C++ 类与 C 不同文件

问题描述

目前在一个项目中,有一组明确的功能与明确的职责相关。因为责任是一个周期性的过程,每次迭代都需要缓冲区和计数器,所以我必须在每个循环之后重置这些函数使用的变量。这些变量大多是静态的,因为每个周期函数被调用数千次。(它是一组数字 FIR 滤波器,可处理每 2 分钟左右传入的 5 秒数据)。变量必须在文件范围内声明,因为函数共享它们。例如重置/初始化功能和实际的过滤功能。

到目前为止,整个项目都是用 C 语言编写的(但很容易支持 C++,因为可能破坏的部分已经包含“extern C {}”)。为了使代码更简洁,我想将函数和变量分组到一个单独的实现文件中。但当然,我也可以使用 C++ 类,我想使用更多。

这些选项之间的本质区别是什么?

我的意思的简化示例: 在这个示例中,我只是保留了程序的结构。例如,第一次迭代在 5 秒内调用了 Filter() 函数 1000 次。然后对于下一次迭代,在实际调用 Filter() 函数之前调用 Reset() 函数,以重置所有使用的缓冲区。

// File-scope variables
static float   buffer[BUFFER_SIZE];
static uint8_t bufferOffset = 0;

// Filter
static float Filter (const float sample)
{
  buffer[bufferOffset] = sample;

  // Removed actual filter code here

  return result;
}

// Reset functions
static void Reset (void)
{
  memset(buffer, 0, sizeof(buffer));
  bufferOffset = 0;
}

标签: c++cclass

解决方案


C 中避免这些共享状态的常用方法是定义一个封装所有相关状态的结构,将其传递给每个函数并单独对其进行操作。

例子:

// buffer.h
#pragma once

// opaque data structure whose content
// isn't available to the outside
struct buffer;

// but you may allocate and free such a data structure
struct buffer *alloc_buffer();
void free_buffer(struct buffer *b);

// and you may operate on it with the following functions
float filter_buffer(struct buffer *b);
void reset_buffer(struct buffer *b);
void add_to_buffer(struct buffer *b, const float *data, size_t size);

源代码如下所示:

// buffer.c
#include "buffer.h"

struct buffer {
    float content[BUFFER_SIZE];
    uint8_t offset;
}

struct buffer *alloc_buffer() {
    return malloc(sizeof(struct buffer));
}

void free_buffer(struct buffer *b) {
    free(b);
}

float filter_buffer(struct buffer *b) {
    // work with b->content and b->offset instead
    // on global buffer and bufferOffset
    return result;
}

void reset_buffer(struct buffer *b) {
    memset(b->content, 0, BUFFER_SIZE);
    b->offset = 0;
}

void add_to_buffer(struct buffer *b, const float *data, size_t num) {
    memcpy(b->content + b->offset, data, sizeof(float) * num);
    b->offset += num;
}

因此,您可以避免全局状态,例如显着简化代码的多线程应用程序。而且由于您返回一个不透明的数据结构,您可以避免泄漏有关缓冲区内部结构的信息。

现在你可以在不同的源文件中使用这个数据结构:

#include "buffer.h"

int main() {
    struct buffer *const b = alloc_buffer();

    // b->content[0] = 1; // <-- error, buffer is an opaque data type and
                          //     you may only use the functions declared in
                          //     buffer.h to access and modify it

    const float data[2] = { 3.1415926, 2.71828 }
    add_to_buffer(b, data, sizeof(data) / sizeof(data[0]));
    const float result = filter_buffer(b);

    return 0;
}

回答你的问题:即使你可以将你的函数和全局状态进一步分成几个编译单元,最终你仍然有一个共享的全局状态。除了在某些特殊情况下,我认为这是代码异味。

上述解决方案或多或少对应于C++解决方案。你定义了一个类,封装了一些状态和对其进行操作的方法。所有实例化的对象都是相互独立的。


推荐阅读