过去,当我希望将回调用作函数参数时,我通常决定使用import java.io.FileOutputStream;
import org.apache.poi.xwpf.usermodel.*;
public class CreateWordTableCellSpacing {
public static void main(String[] args) throws Exception {
XWPFDocument document = new XWPFDocument();
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText("The table");
int cols = 3;
int rows = 3;
XWPFTable table = document.createTable(rows, cols);
table.setWidth("100%");
table.getRow(0).getCell(0).setWidth("20%");
table.getRow(0).getCell(1).setWidth("30%");
table.getRow(0).getCell(2).setWidth("50%");
//set spacing between cells
table.getCTTbl()
.getTblPr()
.addNewTblCellSpacing()
.setType(
org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth.DXA
);
table.getCTTbl()
.getTblPr()
.getTblCellSpacing()
.setW(java.math.BigInteger.valueOf(
180 // 180 TWentieths of an Inch Point (Twips) = 180/20 = 9 pt = 9/72 = 0.125"
));
paragraph = document.createParagraph();
FileOutputStream out = new FileOutputStream("CreateWordTableCellSpacing.docx");
document.write(out);
out.close();
document.close();
}
}
。在我绝对不使用捕获的极少数情况下,我使用std::function
来代替函数声明。
因此,通常我的带有回调参数的声明看起来像这样:
typedef
据我所知,struct Socket
{
void on_receive(std::function<void(uint8_t*, unsigned long)> cb);
}
实际上在运行时做了一些工作,因为必须将其捕获的lambda解析到std::function
模板并移动/复制其捕获(? )。
在阅读有关C ++ 20的新功能后,我认为我可以利用一些概念来避免使用std::function
并为任何可行的函子使用受约束的参数。
这就是我的问题所在:由于我想在将来的某个时候使用回调函子对象,因此我必须存储它们。由于我的回调没有明确的类型,因此我最初的想法是复制(最终在某个时候移动)函子以进行堆放,并使用std::function
标注我留在哪里。
std::vector<void*>
虽然这很好用,但是在实现应该调用函子的方法时,我注意到我只是推迟了unknown / missing类型的问题。据我所知,将template<typename Functor>
concept ReceiveCallback = std::is_invocable_v<Functor, uint8_t*, unsigned long>
&& std::is_same_v<typename std::invoke_result<Functor, uint8_t*, unsigned long>::type, void>
&& std::is_copy_constructible_v<Functor>;
struct Socket
{
std::vector<void*> callbacks;
template<ReceiveCallback TCallback>
void on_receive(TCallback const& callback)
{
callbacks.push_back(new TCallback(callback));
}
}
int main(int argc, char** argv)
{
Socket* sock;
// [...] inialize socket somehow
sock->on_receive([](uint8_t* data, unsigned long length)
{
// NOP for now
});
// [...]
}
转换为函数指针或类似的技巧应该会产生UB-编译器如何知道我实际上是在试图调用一个完全未知的类的operator()?
我考虑过将(复制的)仿函数与指向它的void*
定义的函数指针一起存储,但是我不知道如何将仿函数作为operator()
注入函数中,而没有它我怀疑捕获是否有效。
我的另一种方法是声明一个纯虚拟接口,该接口声明所需的this
函数。不幸的是,我的编译器禁止我将仿函数强制转换为接口,而且我也不认为有合法的方法可以让lambda派生自该函数。
那么,有没有办法解决这个问题,或者我可能只是在滥用模板需求/概念功能?
答案 0 :(得分:13)
您的初始版本恰好使用了std::function
,因为它删除了该类型。如果您想要类型擦除(并且您确实希望这样做,因为您希望用户能够使用任何类型而无需您的代码明确知道该类型是什么),那么您需要某种形式的类型擦除。而且类型擦除不是免费的。
约束适用于模板。您不需要想要模板功能;您需要一个处理类型擦除的可调用函数。
对于必须在提供程序的调用堆栈中生存的回调,std::function
的开销几乎是您所需要的。也就是说,“开销”不是没有意义的。这就是允许您在回调处理器中存储任意未知类型的对象的原因。