R:如何序列化堆中的 C/C++ 数据?
R: how to serialize C/C++ data that lives in the heap?
假设我想包装一些 C 或 C++ 代码,其中包含无法通过 Rcpp 自动映射到 R 类型的数组或向量,但我需要将其传递给 C/C++ 输出有效的函数R对象。例如:
typedef union {
size_t val_sizet;
long double val_longdbl;
} weird_struct
std::vector<weird_struct> an_unmappable_obj;
an_unmappable_obj.resize(2);
an_unmappable_obj[0].val_sizet = 1e20;
an_unmappable_obj[1].val_longdbl = 1.5 * 1e20;
由于这是一个类型的向量,无法转换为任何 R 的原生类型,我想知道如何 return 并以这种方式处理 R/Rcpp 中的这些对象向量(或包含相同值的 C 数组)可以通过 saveRDS
序列化并在 readRDS
.
之后恢复其值
我想这样做的一种方法是通过 memcpy
将对象的内容转换为某种可以转换为 Rcpp 的“NumericVector”或类似类型的 C++ 向量,然后强制转换当需要使用它时,它的第一个元素到所需的 C 类型数组中,但我想知道是否有更好的解决方案。
如果您只想保存 C++ 数据以供稍后在同一会话中使用,最简单的方法是使用外部指针。例如:
// [[Rcpp::export]]
Rcpp::XPtr< std::vector<double> > xptr_example() {
std::vector<double> * x = new std::vector<double>(10);
Rcpp::XPtr< std::vector<double> > p(x, true);
return p;
}
如果您仍然只想序列化,则有很多选择,但您将不得不编写一些额外的自定义代码。
如您所说,您可以将 cast
和 memcpy
放入 R 向量中(使用 RawVector
而不是 NumericVector
),但您必须小心class 只有 "plain old data" 并且没有像指针或文件处理程序这样的特殊内容。
我打算建议将 cereal
库与 Rcereal 一起使用,但这似乎很难与 union
一起使用。不过,如果使用更像 C++ 的 boost::variant
,那效果会很好。这个想法是将对象序列化为原始向量,然后可以使用 saveRDS
和 readRDS
保存和恢复它。这里有一些示例代码:
#include <Rcpp.h>
// [[Rcpp::plugins("cpp11")]]
// [[Rcpp::depends(BH, Rcereal)]]
#include <boost/variant.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/boost_variant.hpp>
#include <cereal/archives/binary.hpp>
#include <sstream>
// [[Rcpp::export]]
Rcpp::RawVector get_weird_vec() {
std::vector<boost::variant<std::size_t, long double>> an_unmappable_obj;
an_unmappable_obj.push_back((std::size_t) 3e9);
an_unmappable_obj.push_back((long double) 1.5 * 1e20);
std::ostringstream os;
cereal::BinaryOutputArchive archive(os);
archive(an_unmappable_obj);
std::string out = os.str();
Rcpp::RawVector res(out.size());
std::copy(out.begin(), out.end(), res.begin());
return res;
}
// [[Rcpp::export]]
void process_weird_vec(Rcpp::RawVector src) {
std::stringstream ss;
ss.write(reinterpret_cast<char*>(&src[0]), src.size());
cereal::BinaryInputArchive archive(ss);
std::vector<boost::variant<std::size_t, long double>> an_unmappable_obj;
archive(an_unmappable_obj);
Rcpp::Rcout << an_unmappable_obj[0] << std::endl;
Rcpp::Rcout << an_unmappable_obj[1] << std::endl;
}
/*** R
raw <- get_weird_vec()
raw
process_weird_vec(raw)
*/
输出:
> raw <- get_weird_vec()
> raw
[1] 02 00 00 00 00 00 00 00 00 00 00 00 00 5e d0 b2 01 00 00 00 00 80 49 41 d4
[26] b0 1a 82 42 40 08 02
> process_weird_vec(raw)
3000000000
1.5e+20
参考文献:
假设我想包装一些 C 或 C++ 代码,其中包含无法通过 Rcpp 自动映射到 R 类型的数组或向量,但我需要将其传递给 C/C++ 输出有效的函数R对象。例如:
typedef union {
size_t val_sizet;
long double val_longdbl;
} weird_struct
std::vector<weird_struct> an_unmappable_obj;
an_unmappable_obj.resize(2);
an_unmappable_obj[0].val_sizet = 1e20;
an_unmappable_obj[1].val_longdbl = 1.5 * 1e20;
由于这是一个类型的向量,无法转换为任何 R 的原生类型,我想知道如何 return 并以这种方式处理 R/Rcpp 中的这些对象向量(或包含相同值的 C 数组)可以通过 saveRDS
序列化并在 readRDS
.
我想这样做的一种方法是通过 memcpy
将对象的内容转换为某种可以转换为 Rcpp 的“NumericVector”或类似类型的 C++ 向量,然后强制转换当需要使用它时,它的第一个元素到所需的 C 类型数组中,但我想知道是否有更好的解决方案。
如果您只想保存 C++ 数据以供稍后在同一会话中使用,最简单的方法是使用外部指针。例如:
// [[Rcpp::export]]
Rcpp::XPtr< std::vector<double> > xptr_example() {
std::vector<double> * x = new std::vector<double>(10);
Rcpp::XPtr< std::vector<double> > p(x, true);
return p;
}
如果您仍然只想序列化,则有很多选择,但您将不得不编写一些额外的自定义代码。
如您所说,您可以将 cast
和 memcpy
放入 R 向量中(使用 RawVector
而不是 NumericVector
),但您必须小心class 只有 "plain old data" 并且没有像指针或文件处理程序这样的特殊内容。
我打算建议将 cereal
库与 Rcereal 一起使用,但这似乎很难与 union
一起使用。不过,如果使用更像 C++ 的 boost::variant
,那效果会很好。这个想法是将对象序列化为原始向量,然后可以使用 saveRDS
和 readRDS
保存和恢复它。这里有一些示例代码:
#include <Rcpp.h>
// [[Rcpp::plugins("cpp11")]]
// [[Rcpp::depends(BH, Rcereal)]]
#include <boost/variant.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/boost_variant.hpp>
#include <cereal/archives/binary.hpp>
#include <sstream>
// [[Rcpp::export]]
Rcpp::RawVector get_weird_vec() {
std::vector<boost::variant<std::size_t, long double>> an_unmappable_obj;
an_unmappable_obj.push_back((std::size_t) 3e9);
an_unmappable_obj.push_back((long double) 1.5 * 1e20);
std::ostringstream os;
cereal::BinaryOutputArchive archive(os);
archive(an_unmappable_obj);
std::string out = os.str();
Rcpp::RawVector res(out.size());
std::copy(out.begin(), out.end(), res.begin());
return res;
}
// [[Rcpp::export]]
void process_weird_vec(Rcpp::RawVector src) {
std::stringstream ss;
ss.write(reinterpret_cast<char*>(&src[0]), src.size());
cereal::BinaryInputArchive archive(ss);
std::vector<boost::variant<std::size_t, long double>> an_unmappable_obj;
archive(an_unmappable_obj);
Rcpp::Rcout << an_unmappable_obj[0] << std::endl;
Rcpp::Rcout << an_unmappable_obj[1] << std::endl;
}
/*** R
raw <- get_weird_vec()
raw
process_weird_vec(raw)
*/
输出:
> raw <- get_weird_vec()
> raw
[1] 02 00 00 00 00 00 00 00 00 00 00 00 00 5e d0 b2 01 00 00 00 00 80 49 41 d4
[26] b0 1a 82 42 40 08 02
> process_weird_vec(raw)
3000000000
1.5e+20
参考文献: