[为清晰起见,更新了组织和内容]
真正的问题
对于C来说,帮助程序员在他/她打字的同时,对项目特定的类似printf的调试函数进行安全和正确的调用会有什么好方法?
C宏?C包装函数?代码编辑器宏或模板?其他?
背景问题和答案
许多软件使用printf或类似printf的函数进行调试,无论是出现问题时是临时还是调试日志.但它容易出错.
Q1:我们怎么知道?
A1:静态分析器有printf-mismatch错误的类别 - 这是一类常见的错误 - 我经常看到这些工具在C代码上调出这些警告.
Q2:这个错误的子类是什么?
A2:主要是格式说明符错误,格式说明符号错误.通常真正的错误是相反的:错误的变量类型,或打印输出的错误数量的变量.
Q3:我们为什么关心?
A3:充其量,会导致错误的日志记录信息并阻碍调试.最糟糕的是,崩溃软件.
问题4:有没有人试图对此问题采取任何措施?
A4:当然,虽然我没有看到任何C(而不是C++或其他),例如:
http://www.ddj.com/cpp/184401999?pgno=1 http://mi.eng.cam.ac.uk/~er258/cvd/tag/html/group__printf.html
在这些产品和其他产品中我缺少的是什么,除了现在我正在研究用C语言编写并且需要解决C问题的产品,这是因为它们是事后解决方案.他们能避免碰撞,并能提供的什么地方出了错警示说明,并说出事了,但他们肯定无法猜测程序员的意图(见电除尘器.Q&上述A#2).
Q5:为什么使用printf这么容易出错?
A5:因为编写printf调用需要程序员在一行上混合变量的类型和数量,格式说明符,自由文本字符串常量和标点符号 - 所有这些看起来非常相似.
我目前正在使用新的c ++ 0x可变参数模板,这很有趣,虽然我对成员实例化的过程有疑问.
在这个例子中,我试图模拟强类型枚举,可以选择随机有效的强枚举(这用于单元测试).
#include<vector>
#include<iostream>
using namespace std;
template<unsigned... values> struct sp_enum;
/*
this is the solution I found, declaring a globar var
vector<unsigned> _data;
and it work just fine
*/
template<> struct sp_enum<>{
static const unsigned _count = 0;
static vector<unsigned> _data;
};
vector<unsigned> sp_enum<>::_data;
template<unsigned T, unsigned... values>
struct sp_enum<T, values...> : private sp_enum<values...>{
static const unsigned _count = sp_enum<values...>::_count+1;
static vector<unsigned> _data;
sp_enum( ) : sp_enum<values...>(values...) {_data.push_back(T);}
sp_enum(unsigned v ) {_data.push_back(v);}
sp_enum(unsigned v, unsigned...) : sp_enum<values...>(values...) …Run Code Online (Sandbox Code Playgroud) 我有一个带有状态机的类,并且希望有一个入口点将事件传递给状态机。该事件伴随有事件特定的数据,然后我希望将其分发给处理程序。所以看起来像这样...
class X
{
public:
...
template<typename... A> void fsm(eEvent eventId, const A&... eventData);
private:
...
void eventA(int a, double b);
void eventB(std::string a);
void eventC(unsigned long a);
Run Code Online (Sandbox Code Playgroud)
};
...看起来像这样的调用...
X x;
x.fsm(eEventA, -1, 2.0);
x.fsm(eEventB, "xyz");
x.fsm(eEventC, 42);
Run Code Online (Sandbox Code Playgroud)
我在弄清楚如何获取模板函数来调用正确的处理程序时遇到了麻烦。如果我只是打开eventId并通过变量参数,它将不会编译,因为对于所有参数组合都不存在处理程序(例如,没有eventA()处理程序接受eventB()参数,我不会仍然要)。
我的猜测是有一些优雅的方法可以做到这一点,但这让我难以理解。
我有以下模板功能:
template <typename...Args, typename Func>
void call(const char *name, Args...args, Func f)
{
f(3);
}
Run Code Online (Sandbox Code Playgroud)
当我尝试使用它时,就像
call("test", 1, 2, 3, [=](int i) { std::cout<< i; });
Run Code Online (Sandbox Code Playgroud)
编译器抱怨它无法推断模板参数Func.如何解决这个问题,知道args除了函数指针之外的任何类型.
我有一些从序列化数据中读取各种类型的函数,例如:
class DataDeserializer
{
int getInt();
std::string getString();
MyClass getMyClass();
}
Run Code Online (Sandbox Code Playgroud)
然后我有各种带有任意参数的回调函数,例如:
void callbackA (int, int, int);
void callbackB (int, std::string);
void callbackC (std::string, int, MyClass, int);
Run Code Online (Sandbox Code Playgroud)
我想调用从反序列化数据流中读取的参数的各种回调.我想要的是尽可能自动化样板代码.我想也许我可以使用模板.如果我有某种Dispatcher类,例如:
template <SOMETHING??> class Dispatcher
{
void dispatch()
{
// ????
}
SOMEFUNCTIONTYPE callback;
DataDeserializer myDeserializer;
};
Run Code Online (Sandbox Code Playgroud)
然后声明各种特定的调度员:
Dispatcher<int,int,int> myDispatcherA (deserializer, callbackA);
Dispatcher<int,std::string> myDispatcherB (deserializer, callbackB);
Dispatcher<std::string,int,MyClass,int> myDispatcherC (deserializer, callbackC);
Run Code Online (Sandbox Code Playgroud)
然后,当我想发送时,我只是打电话给:
myDispatcherB.dispatch();
Run Code Online (Sandbox Code Playgroud)
下面会扩展到这样的东西:
void dispatch()
{
callback (myDeserializer.getString(), myDeserializer.getInt(), myDeserializer.getMyClass(), myDeserializer.getInt());
}
Run Code Online (Sandbox Code Playgroud)
这可能与C++ 11可变参数模板有关吗?我已经阅读了一些关于它们的内容,似乎递归使用了很多.
以下代码不起作用.它的目的是将参数传递给可变参数基类.这是可能的,如果是这样,实施它的正确方法是什么?(Clang的错误信息是:an initializer for a delegating constructor must appear alone,我不明白.)
template<class... Visitors>
class Visited: public Visitors...
{
public:
template<class F>
Visited(const F& f): F(f) {}
template<class F, class... Rest>
Visited(const F& f, const Rest&... r):
F(f), Visited(r...) {}
};
struct A {};
struct B {};
struct C {};
int main()
{
A a;
B b;
C c;
Visited<A,B,C> v(a, b, c);
}
Run Code Online (Sandbox Code Playgroud) 我希望能够使用ostringstream将多个不同的参数组合成一个字符串.这样我就可以记录生成的单个字符串而不会出现任何随机问题.
我到目前为止:
template <typename T>
void MagicLog(T t)
{
std::cout << t << std::endl;
}
template<typename T, typename... Args>
void MagicLog(T t, Args... args) // recursive variadic function
{
std::cout << t << std::endl;
MagicLog(args...);
}
template<typename T, typename... Args>
void LogAll(int logType, T t, Args... args)
{
std::ostringstream oss;
MagicLog(t);
MagicLog(args...);
//Log(logType, oss.str());
}
Run Code Online (Sandbox Code Playgroud)
所以我需要用我在LogAll函数中创建的oss替换std :: cout,我尝试将它作为参数传递给其他函数,但它抱怨"删除函数"...
那么:我怎样才能获得一个递归的可变参数函数来接受另一个参数ostringstream?
想想这个,
struct Person {
std::string name;
Person (const std::string& n) : name(n) {}
std::string getName(int, char) const {return name;} // int, char play no role in this
// simple example, but let's suppose that they are needed.
} *Bob = new Person("Bob"), *Frank = new Person("Frank"), *Mark = new Person("Mark"),
*Tom = new Person("Tom"), *Zack = new Person("Zack");
const std::vector<Person*> people = {Bob, Frank, Mark, Tom, Zack};
Run Code Online (Sandbox Code Playgroud)
因为people按名称排序,我们可以执行二进制搜索以查找people具有特定名称的元素.我希望这个看起来像是这样的
Person* person = binarySearch (people, "Tom",
[](Person* p, …Run Code Online (Sandbox Code Playgroud) 我想在coq中制作一个Ltac战术,它可以采用1或3个参数.我ltac_No_arg在LibTactics模块中已经阅读过,但如果我理解正确,我将不得不调用我的策略:
Coq < mytactic arg_1 ltac_no_arg ltac_no_arg.
Run Code Online (Sandbox Code Playgroud)
这不是很方便.
有没有办法得到这样的结果?:
Coq < mytactic arg_1.
Coq < mytactic arg_1 arg_2 arg_3.
Run Code Online (Sandbox Code Playgroud) 我正在Rust中构建一个Redis模块。我找到了一些很好的例子,但是在处理与应该接受可变参数的C函数接口时,我陷入了困境。
Redis Module C SDK具有一个名为的函数RedisModule_Call,该函数接受一些特定的参数,然后接受n代表Redis命令的参数。从Redis Module SDK文档(在C中):
RedisModuleCallReply *reply;
reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");
Run Code Online (Sandbox Code Playgroud)
RedisModule_Call的前三个参数是特定的,但其余的表示Redis命令,可以轻松地包含数百个参数。
在Rust中,我遵循Redis-Cell中的模式,Redis-Cell是在Rust中成功实现的Redis模块。该模块很棒,但是处理此特定问题的方式非常有限。实际上,它以某种蛮力的方式接受了多达三个参数:
pub fn call(&self, command: &str, args: &[&str]) -> Result<Reply, CellError> {
// ... code ...
let raw_reply = match args.len() {
1 => raw::call1::call(/* ... */),
2 => raw::call2::call(/* ... */),
// ...
Run Code Online (Sandbox Code Playgroud)
这些call1和call2函数实际上只是处理不同参数长度的存根:
pub mod call2 {
use redis::raw;
pub fn call(
ctx: *mut raw::RedisModuleCtx,
cmdname: *const u8,
fmt: *const …Run Code Online (Sandbox Code Playgroud)