使用Rcpp模块暴露带有参考参数的c ++类方法时出错

时间:2019-03-16 23:14:55

标签: c++ r rcpp

我的目标是建立一个数据集类和一个模型类,并将它们都暴露给R。该模型类有一个train()方法,该方法引用一个数据集实例,这似乎在我问题的根源。这就是它的样子

//glue.cpp

#include <Rcpp.h>

class MyData
{
public:
  MyData() = default;
};

class MyModel
{
public:
  MyModel() = default;
  void train(const MyData& data) { Rcpp::Rcout << "training model... "; };
};

// Expose MyData
RCPP_MODULE(MyData){
  Rcpp::class_<MyData>("MyData")
  .constructor()
  ;
}

// Expose MyModel
RCPP_MODULE(MyModel){
  Rcpp::class_<MyModel>("MyModel")
  .constructor()
  .method("train", &MyModel::train)
  ;
}

(我应该注意,这个文件胶水.cpp嵌入在R包中。)当我删除此行.method("train", &MyModel::train)时,可以通过pkgbuild::compile_dll()进行编译而不会出现错误。有了它,我在下面得到了讨厌的错误

─  installing *source* package ‘SimpleCppModel’ ...
   ** libs
   clang++  -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I"/Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include" -I/usr/local/include  -std=c++14 -fPIC  -Wall -g -O2  -c RcppExports.cpp -o RcppExports.o
   clang++  -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I"/Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include" -I/usr/local/include  -std=c++14 -fPIC  -Wall -g -O2  -c glue.cpp -o glue.o
   In file included from glue.cpp:3:
   In file included from /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp.h:27:
   In file included from /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/RcppCommon.h:168:
   In file included from /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/as.h:25:
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/internal/Exporter.h:31:28: error: no matching constructor for initialization of 'MyData'
                       Exporter( SEXP x ) : t(x){}
                                            ^ ~
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/as.h:87:41: note: in instantiation of member function 'Rcpp::traits::Exporter<MyData>::Exporter' requested here
               ::Rcpp::traits::Exporter<T> exporter(x);
                                           ^
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/as.h:152:26: note: in instantiation of function template specialization 'Rcpp::internal::as<MyData>' requested here
           return internal::as<T>(x, typename traits::r_type_traits<T>::r_category());
                            ^
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/InputParameter.h:72:54: note: in instantiation of function template specialization 'Rcpp::as<MyData>' requested here
           ConstReferenceInputParameter(SEXP x_) : obj( as<T>(x_) ){}
                                                        ^
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/module/Module_generated_CppMethod.h:129:58: note: in instantiation of member function 'Rcpp::ConstReferenceInputParameter<MyData>::ConstReferenceInputParameter' requested here
           typename Rcpp::traits::input_parameter<U0>::type x0(args[0]);
                                                            ^
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/module/Module_generated_CppMethod.h:127:5: note: in instantiation of member function 'Rcpp::CppMethod1<MyModel, void, const MyData &>::operator()' requested here
       CppMethod1( Method m) : method_class(), met(m) {} 
       ^
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/module/Module_generated_method.h:59:27: note: in instantiation of member function 'Rcpp::CppMethod1<MyModel, void, const MyData &>::CppMethod1' requested here
       AddMethod( name_, new CppMethod1<Class,RESULT_TYPE,U0>(fun), valid, docstring);
                             ^
   glue.cpp:30:4: note: in instantiation of function template specialization 'Rcpp::class_<MyModel>::method<void, const MyData &>' requested here
     .method("train", &MyModel::train)
      ^
   glue.cpp:5:7: note: candidate constructor (the implicit copy constructor) not viable: cannot convert argument of incomplete type 'SEXP' (aka 'SEXPREC *') to 'const MyData' for 1st argument
   class MyData
         ^
   glue.cpp:5:7: note: candidate constructor (the implicit move constructor) not viable: cannot convert argument of incomplete type 'SEXP' (aka 'SEXPREC *') to 'MyData' for 1st argument
   glue.cpp:8:3: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
     MyData() = default;
     ^
   1 error generated.
   make: *** [glue.o] Error 1
   ERROR: compilation failed for package ‘SimpleCppModel’
─  removing ‘/private/var/folders/dn/9lp6j6j14t1137ftnnk27wyw0000gn/T/RtmpBqDLoF/devtools_install_4d775ed17ea2/SimpleCppModel’
Error in processx::run(bin, args = real_cmdargs, stdout_line_callback = real_callback(stdout),  : 
  System command error

有什么作用?

2 个答案:

答案 0 :(得分:3)

正如Dirk在评论中所提到的,您将需要as<>的{​​{1}}和wrap专业化知识。在您的情况下,可以使用“扩展Rcpp”小插图中最简单的解决方案:MyData当您包含Rcpp中的哪个标头时,您只需要小心:

RCPP_EXPOSED_CLASS

正如您在答案中发现的那样,它甚至在之后(包括#include <Rcpp.h> class MyData { public: MyData() = default; }; RCPP_EXPOSED_CLASS(MyData) class MyModel { public: MyModel() = default; void train(const MyData& data) { Rcpp::Rcout << "training model... " << std::endl; }; }; // Expose MyData RCPP_MODULE(MyData){ Rcpp::class_<MyData>("MyData") .constructor() ; } // Expose MyModel RCPP_MODULE(MyModel){ Rcpp::class_<MyModel>("MyModel") .constructor() .method("train", &MyModel::train) ; } /***R myData <- new(MyData) myModel <- new(MyModel) myModel$train(myData) */ )都有效。 不过,仔细阅读提到的画廊文章和小插图仍然有意义。

答案 1 :(得分:2)

首先,我应该指出问题中的示例过于简单。实际上,MyData类具有$db = mysqli_connect('localhost', 'root', '', 'multi_login'); // variable declaration $username = ""; $firstname = ""; $lastname = ""; $errors = array(); // call the register() function if register_btn is clicked if (isset($_POST['register_btn'])) { register(); } // call the login() function if register_btn is clicked if (isset($_POST['login_btn'])) { login(); } if (isset($_GET['logout'])) { session_destroy(); unset($_SESSION['user']); header("location: ../login.php"); } // REGISTER USER function register(){ global $db, $errors; // receive all input values from the form $username = e($_POST['username']); $firstname = e($_POST['firstname']); $lastname = e($_POST['lastname']); $password_1 = e($_POST['password_1']); $password_2 = e($_POST['password_2']); // form validation: ensure that the form is correctly filled if (empty($username)) { array_push($errors, "Username is required"); } if (empty($firstname)) { array_push($errors, "First Name is required"); } if (empty($lastname)) { array_push($errors, "Last Name is required"); } if (empty($password_1)) { array_push($errors, "Password is required"); } if ($password_1 != $password_2) { array_push($errors, "The two passwords do not match"); } // register user if there are no errors in the form if (count($errors) == 0) { $password = md5($password_1);//encrypt the password before saving in the database if (isset($_POST['user_type'])) { $user_type = e($_POST['user_type']); $query = "INSERT INTO users (username, firstname, lastname, user_type, password) VALUES('$username', '$firstname', '$lastname', '$user_type', '$password')"; mysqli_query($db, $query); $_SESSION['success'] = "New user successfully created!!"; header('location: home.php'); }else{ $query = "INSERT INTO users (username, firstname, lastname, user_type, password) VALUES('$username', '$firstname', '$lastname', 'user', '$password')"; mysqli_query($db, $query); // get id of the created user $logged_in_user_id = mysqli_insert_id($db); $_SESSION['user'] = getUserById($logged_in_user_id); // put logged in user in session $_SESSION['success'] = "You are now logged in"; header('location: index.php'); } } } // return user array from their id function getUserById($id){ global $db; $query = "SELECT * FROM users WHERE id=" . $id; $result = mysqli_query($db, $query); $user = mysqli_fetch_assoc($result); return $user; } // LOGIN USER function login(){ global $db, $username, $errors; // grap form values $username = e($_POST['username']); $password = e($_POST['password']); // make sure form is filled properly if (empty($username)) { array_push($errors, "Username is required"); } if (empty($password)) { array_push($errors, "Password is required"); } // attempt login if no errors on form if (count($errors) == 0) { $password = md5($password); $query = "SELECT * FROM users WHERE username='$username' AND password='$password' LIMIT 1"; $results = mysqli_query($db, $query); if (mysqli_num_rows($results) == 1) { // user found // check if user is admin or user $logged_in_user = mysqli_fetch_assoc($results); if ($logged_in_user['user_type'] == 'admin') { $_SESSION['user'] = $logged_in_user; $_SESSION['success'] = "You are now logged in"; header('location: admin/home.php'); }else{ $_SESSION['user'] = $logged_in_user; $_SESSION['success'] = "You are now logged in"; header('location: index.php'); } }else { array_push($errors, "Wrong username/password combination"); } } } function isLoggedIn() { if (isset($_SESSION['user'])) { return true; }else{ return false; } } function isAdmin() { if (isset($_SESSION['user']) && $_SESSION['user']['user_type'] == 'admin' ) { return true; }else{ return false; } } // escape string function e($val){ global $db; return mysqli_real_escape_string($db, trim($val)); } function display_error() { global $errors; if (count($errors) > 0){ echo '<div class="error">'; foreach ($errors as $error){ echo $error .'<br>'; } echo '</div>'; } } 之类的依赖于void fill(const Rcpp::NumericMatrix& dat) { Rcpp::Rcout << "filling data... " << std::endl; };的方法,因此在声明MyData之前,我必须 Rcpp::NumericMatrix

我找到了两个解决方案,但是我还没有足够地使用它们来了解是否有任何“陷阱”。

解决方案1-RCPP_EXPOSED_CLASS()宏

在这里,我将RCPP_EXPOSED_CLASS()宏用作described by Romain Francois。 (感谢拉尔夫,向我指出了这一点。)

#include <Rcpp.h>

解决方案2-使用Rcpp :: XPtr

此解决方案基于design pattern discussed here。基本上,我只是创建一个指向C ++对象的指针,并使用包装函数来处理它。这是一个看起来可能如何的示例。

#include <Rcpp.h>

class MyData;
class MyModel;

RCPP_EXPOSED_CLASS(MyData)
RCPP_EXPOSED_CLASS(MyModel)

class MyData
{
public:
  MyData() = default;
  void fill(const Rcpp::NumericMatrix& dat) { Rcpp::Rcout << "filling data... " << std::endl; };
};

  class MyModel
  {
  public:
    MyModel() = default;
    void train(const MyData& data) { Rcpp::Rcout << "training model... " << std::endl; };
  };


// Expose MyData
RCPP_MODULE(MyData){
  Rcpp::class_<MyData>("MyData")
  .constructor()
  .method("fill", &MyData::fill)
  ;
}

// Expose MyModel
RCPP_MODULE(MyModel){
  Rcpp::class_<MyModel>("MyModel")
  .constructor()
  .method("train", &MyModel::train)
  ;
}

/***R
myData <- new(MyData)
myModel <- new(MyModel)
myModel$train(myData)
*/

此方法的缺点是AFAIK,没有明显的方法可以检查XPtr指向的对象的类型。例如,对于我来说如何使#include <Rcpp.h> class MyData { public: MyData() = default; void fill(const Rcpp::NumericMatrix& dat) { Rcpp::Rcout << "filling data... " << std::endl; }; }; class MyModel { public: MyModel() = default; void train(const MyData& data) { Rcpp::Rcout << "training model... " << std::endl; }; }; // [[Rcpp::export]] SEXP make_dataset() { MyData* datPtr = new MyData(); Rcpp::XPtr<MyData> datXPtr(datPtr); return datXPtr; } // [[Rcpp::export]] SEXP make_model() { MyModel* mdlPtr = new MyModel(); Rcpp::XPtr<MyModel> mdlXPtr(mdlPtr); return mdlXPtr; } // [[Rcpp::export]] void train_model(SEXP mdlXPtr, SEXP datXPtr) { Rcpp::XPtr<MyModel> mdlPtr(mdlXPtr); Rcpp::XPtr<MyData> datPtr(datXPtr); mdlPtr->train(*datPtr); } /***R myData <- make_dataset() myModel <- make_model() train_model(myModel, myData) */ 之类的错误呼叫无效是不明显的。