首页 > 解决方案 > 使用 gtest 模拟 boost 共享内存派生类

问题描述

我有一个简单的 CPP 类存储我的项目的一些配置。

这个类是使用 boost 进程间共享内存存储的,因此可以从我的服务器上运行的不同进程访问。

现在,我想在我的程序上运行一些测试——所以我想模拟我的共享内存对象的功能。为了使用 gtest 做到这一点,我创建了一个基本配置类,我的模拟类和共享内存类将从该类派生。

为了正确使用 gtest,我想模拟的基类函数必须是虚拟的,但根据boost 文档,共享内存不能包含虚拟函数,所以这是一种死锁。

我的基类示例:

class Configuration {
protected:
    YAML::Node YmlFile_;
public:
    struct System {
    private:
        float num1;
        float num2;
    public:
        virtual ~System(){}
        virtual float GetNum1() const {
            return num1;
        }

        virtual float GetNum2() const {
            return num2;
        }
    struct Logs{
    private:
        float num3;
        float num4;
    public:
        virtual ~Logs(){}
        virtual float GetNum3() const {
            return num3;
        }

        virtual float GetNum4() const {
            return num4;
        }
    Logs logs;
    System system;
    virtual System* GetSystem(){}
    virtual Logs* GetLogs(){}

在模拟类上,我希望模拟函数以获取结构(GetSystem,GetLogs),然后模拟它们的返回值,但仍然能够保存将保存在共享内存中的“真实”派生类配置.

有任何想法吗..?

标签: c++boostgoogletestboost-interprocess

解决方案


先说原则:

您不必使用虚函数来模拟。

在不能使用运行时多态类型的地方,可以使用静态多态。

但在这种情况下,将配置接口与实现完全分离似乎更好。

实现您的接口不是从共享内存容器派生的(“配置源是共享内存对象”)。而是说“配置源有-一个共享内存对象”。

其他关键问题:

  • 是什么让 YAML::Node 对共享内存安全?可能不是,因为我没有看到指定的分配器,而且它肯定涉及动态分配的内存以及内部指针。

    我认为这种方法很容易因此而死在水中。

  • 如果实际来源是 YAML,为什么不直接共享文件,而不是高度复杂的共享内存?(我们在这里只是在摸索表面。我们甚至没有提到同步)。

    从一开始,文件系统就是计算机上进程的事实上的“共享内存”。

示例解耦接口和实现

接口使得实现可以解耦,但正如您所注意到的,继承通常使得它们不是。

为什么不写这样的东西:

struct ConfigData {
    struct System {
        float num1;
        float num2;

        struct Logs {
            float num3;
            float num4;
        } logs;
    } system;
};

现在制作一个共享接口(我会为演示简化它):

struct IConfiguration {
    virtual ConfigData const& getData() const = 0;
};

所以你可以有你的 YAML 后端:

class YAMLConfiguration : public IConfiguration {
  public:
    YAMLConfiguration(std::istream& is) : _node(YAML::Load(is)) {
        parse(_node, _data);
    }

    virtual ConfigData const& getData() const override {
        return _data;
    }
  private:
    YAML::Node _node;
    ConfigData _data;
};

或共享内存实现:

#include <boost/interprocess/managed_shared_memory.hpp>
namespace bip = boost::interprocess;

class SharedConfiguration : public IConfiguration {
  public:
    SharedConfiguration(std::string name) 
        : _shm(bip::open_or_create, name.c_str(), 10ul << 10),
          _data(*_shm.find_or_construct<ConfigData>("ConfigData")())
    { }

    virtual ConfigData const& getData() const override {
        return _data;
    }
  private:
    bip::managed_shared_memory _shm;
    ConfigData& _data;
};

完整演示

在科利鲁¹

struct ConfigData {
    struct System {
        float num1 = 77;
        float num2 = 88;

        struct Logs {
            float num3 = 99;
            float num4 = 1010;
        } logs;
    } system;
};

struct IConfiguration {
    virtual ConfigData const& getData() const = 0;
};

///////// YAML Backend
#include <yaml-cpp/yaml.h>
static bool parse(YAML::Node const& node, ConfigData::System::Logs& data) {
    data.num3 = node["num3"].as<float>();
    data.num4 = node["num4"].as<float>();
    return true;
}
static bool parse(YAML::Node const& node, ConfigData::System& data) {
    data.num1 = node["num1"].as<float>();
    data.num2 = node["num2"].as<float>();
    parse(node["Logs"], data.logs);
    return true;
}
static bool parse(YAML::Node const& node, ConfigData& data) {
    parse(node["System"], data.system);
    return true;
}

class YAMLConfiguration : public IConfiguration {
  public:
    YAMLConfiguration(std::istream& is) : _node(YAML::Load(is)) {
        parse(_node, _data);
    }

    virtual ConfigData const& getData() const override {
        return _data;
    }
  private:
    YAML::Node _node;
    ConfigData _data;
};

///////// Shared Memory Backend
#include <boost/interprocess/managed_shared_memory.hpp>
namespace bip = boost::interprocess;

class SharedConfiguration : public IConfiguration {
  public:
    SharedConfiguration(std::string name) 
        : _shm(bip::open_or_create, name.c_str(), 10ul << 10),
          _data(*_shm.find_or_construct<ConfigData>("ConfigData")())
    { }

    virtual ConfigData const& getData() const override {
        return _data;
    }
  private:
    bip::managed_shared_memory _shm;
    ConfigData& _data;
};


#include <iostream>
void FooFunction(IConfiguration const& cfg) {
    std::cout << "Logs.num3:" << cfg.getData().system.logs.num3 << "\n";
}

void FakeApplication() {
    std::cout << "Hello from FakeApplication\n";
    std::istringstream iss(R"(
System:
    num1: 0.1
    num2: 0.22
    Logs:
        num3: 0.333
        num4: 0.4444
    )");

    YAMLConfiguration config(iss);
    FooFunction(config);
}

void FakeTests() {
    std::cout << "Hello from FakeTests\n";
    SharedConfiguration config("shared_memory_name");
    FooFunction(config);
}

int main() {
    FakeApplication();
    FakeTests();
}

印刷

Hello from FakeApplication
Logs.num3:0.333
Hello from FakeTests
Logs.num3:99

总结与注意

简而言之,在使用共享内存之前要三思而后行。它并不像你想的那么简单。

很可能,您的一些配置值将不是 POD 数据类型(您知道,可能是字符串),突然间您将不得不关心分配器:

此外,不要忘记访问共享内存的进程之间的同步。


¹ Coliru 没有 yaml-cpp,但您可以使用 managed_mapped_file 显示共享实现:Live On Coliru


推荐阅读